Merge lp:~danilo/maas/django-upgrade-method-fixes into lp:~maas-committers/maas/trunk

Proposed by Данило Шеган
Status: Merged
Approved by: Данило Шеган
Approved revision: no longer in the source branch.
Merged at revision: 6089
Proposed branch: lp:~danilo/maas/django-upgrade-method-fixes
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 522 lines (+85/-91)
17 files modified
docs/development/notifications.rst (+1/-1)
src/apiclient/testing/django.py (+6/-1)
src/maasserver/api/doc_handler.py (+4/-6)
src/maasserver/api/tests/test_doc.py (+6/-7)
src/maasserver/djangosettings/settings.py (+29/-27)
src/maasserver/forms/__init__.py (+0/-2)
src/maasserver/models/largefile.py (+3/-2)
src/maasserver/models/tests/test_blockdevice.py (+4/-4)
src/maasserver/models/tests/test_physicalblockdevice.py (+8/-10)
src/maasserver/urls.py (+6/-2)
src/maasserver/utils/__init__.py (+1/-4)
src/maasserver/utils/tests/test_views.py (+1/-2)
src/maasserver/utils/views.py (+1/-0)
src/maasserver/views/account.py (+3/-7)
src/maasserver/views/prefs.py (+5/-6)
src/maasserver/views/settings.py (+4/-5)
src/maasserver/views/tests/test_general.py (+3/-5)
To merge this branch: bzr merge lp:~danilo/maas/django-upgrade-method-fixes
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+325747@code.launchpad.net

Commit message

Stop using settings, methods, functions and attributes that have been dropped between Django 1.8 and 1.11, fix up issues in our code that surfaced since 1.11 is stricter.

Description of the change

Django has gotten stricter between 1.8 and 1.11, which highlighted a few things in our code:

 - we were passing path to BlockDevice/PhysicalBlockDevice models in tests, yet this was a read-only property on the model for some time already
 - PackageRepository has long lost the "description" field, yet Django silently ignored us setting it in the forms

The rest of the changes are basically following the suggested upgrade paths for Django code.

Dependencies
============

To test with Django 1.11, you need to add the following two PPAs:
  sudo apt-add-repository ppa:danilo/django-1.11
  sudo apt-add-repository ppa:danilo/django-piston3

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good. One minor nit below that I won't block you on.

I tested this branch with my local MAAS (on Django 1.8), so I'm pretty sure this doesn't cause a regression.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/development/notifications.rst'
2--- docs/development/notifications.rst 2017-02-15 11:24:50 +0000
3+++ docs/development/notifications.rst 2017-06-16 07:52:25 +0000
4@@ -184,7 +184,7 @@
5
6 should be done with ``find_for_user``:
7
8- >>> Notification.objects.find_for_user(user)
9+ >>> list(Notification.objects.find_for_user(user))
10 [<Notification ...]
11
12
13
14=== modified file 'src/apiclient/testing/django.py'
15--- src/apiclient/testing/django.py 2015-12-01 18:12:59 +0000
16+++ src/apiclient/testing/django.py 2017-06-16 07:52:25 +0000
17@@ -23,7 +23,8 @@
18
19
20 # Django 1.6 needs to be configured before we can import WSGIRequest.
21-settings.configure(DEBUG=True)
22+if not settings.configured:
23+ settings.configure(DEBUG=True)
24
25
26 class Base64Decoder:
27@@ -65,6 +66,10 @@
28 """
29 handler = MemoryFileUploadHandler()
30 meta = {
31+ "CONTENT_TYPE": headers["Content-Type"],
32+ "CONTENT_LENGTH": headers["Content-Length"],
33+ # To make things even more entertaining, 1.8 prefixed meta vars
34+ # with "HTTP_" and 1.11 does not.
35 "HTTP_CONTENT_TYPE": headers["Content-Type"],
36 "HTTP_CONTENT_LENGTH": headers["Content-Length"],
37 }
38
39=== modified file 'src/maasserver/api/doc_handler.py'
40--- src/maasserver/api/doc_handler.py 2017-01-28 00:51:47 +0000
41+++ src/maasserver/api/doc_handler.py 2017-06-16 07:52:25 +0000
42@@ -66,8 +66,7 @@
43 from textwrap import dedent
44
45 from django.http import HttpResponse
46-from django.shortcuts import render_to_response
47-from django.template import RequestContext
48+from django.shortcuts import render
49 from docutils import core
50 from maasserver.api.doc import (
51 describe_api,
52@@ -172,10 +171,9 @@
53 # Generate the documentation and keep it cached. Note that we can't do
54 # that at the module level because the API doc generation needs Django
55 # fully initialized.
56- return render_to_response(
57- 'maasserver/api_doc.html',
58- {'doc': reST_to_html_fragment(render_api_docs())},
59- context_instance=RequestContext(request))
60+ return render(
61+ request, 'maasserver/api_doc.html',
62+ {'doc': reST_to_html_fragment(render_api_docs())})
63
64
65 def describe(request):
66
67=== modified file 'src/maasserver/api/tests/test_doc.py'
68--- src/maasserver/api/tests/test_doc.py 2017-01-28 00:51:47 +0000
69+++ src/maasserver/api/tests/test_doc.py 2017-06-16 07:52:25 +0000
70@@ -15,7 +15,6 @@
71
72 from django.conf.urls import (
73 include,
74- patterns,
75 url,
76 )
77 from django.core.exceptions import ImproperlyConfigured
78@@ -84,7 +83,7 @@
79 def test_urlpatterns_empty(self):
80 # No resources are found in empty modules.
81 module = self.make_module()
82- module.urlpatterns = patterns("")
83+ module.urlpatterns = []
84 self.assertSetEqual(set(), find_api_resources(module))
85
86 def test_urlpatterns_not_present(self):
87@@ -95,7 +94,7 @@
88 def test_urlpatterns_with_resource_for_incomplete_handler(self):
89 # Resources for handlers that don't specify resource_uri are ignored.
90 module = self.make_module()
91- module.urlpatterns = patterns("", url("^foo", BaseHandler))
92+ module.urlpatterns = [url("^foo", BaseHandler)]
93 self.assertSetEqual(set(), find_api_resources(module))
94
95 def test_urlpatterns_with_resource(self):
96@@ -105,7 +104,7 @@
97 handler = type("\m/", (BaseHandler,), {"resource_uri": True})
98 resource = Resource(handler)
99 module = self.make_module()
100- module.urlpatterns = patterns("", url("^metal", resource))
101+ module.urlpatterns = [url("^metal", resource)]
102 self.assertSetEqual({resource}, find_api_resources(module))
103
104 def test_urlpatterns_with_resource_hidden(self):
105@@ -117,7 +116,7 @@
106 })
107 resource = Resource(handler)
108 module = self.make_module()
109- module.urlpatterns = patterns("", url("^metal", resource))
110+ module.urlpatterns = [url("^metal", resource)]
111 self.assertSetEqual(set(), find_api_resources(module))
112
113 def test_nested_urlpatterns_with_handler(self):
114@@ -126,8 +125,8 @@
115 resource = Resource(handler)
116 module = self.make_module()
117 submodule = self.make_module()
118- submodule.urlpatterns = patterns("", url("^metal", resource))
119- module.urlpatterns = patterns("", ("^genre/", include(submodule)))
120+ submodule.urlpatterns = [url("^metal", resource)]
121+ module.urlpatterns = [url("^genre/", include(submodule))]
122 self.assertSetEqual({resource}, find_api_resources(module))
123
124 def test_smoke(self):
125
126=== modified file 'src/maasserver/djangosettings/settings.py'
127--- src/maasserver/djangosettings/settings.py 2017-05-25 13:19:34 +0000
128+++ src/maasserver/djangosettings/settings.py 2017-06-16 07:52:25 +0000
129@@ -187,25 +187,31 @@
130 # Make this unique, and don't share it with anybody.
131 SECRET_KEY = 'zk@qw+fdhu_b4ljx+pmb*8sju4lpx!5zkez%&4hep_(o6y1nf0'
132
133-# List of callables that know how to import templates from various sources.
134-TEMPLATE_LOADERS = (
135- 'django.template.loaders.filesystem.Loader',
136- 'django.template.loaders.app_directories.Loader',
137- # 'django.template.loaders.eggs.Loader',
138-)
139-
140-TEMPLATE_CONTEXT_PROCESSORS = (
141- "django.contrib.auth.context_processors.auth",
142- "django.core.context_processors.debug",
143- "django.core.context_processors.i18n",
144- "django.core.context_processors.request",
145- "django.core.context_processors.media",
146- "django.core.context_processors.static",
147- # "django.core.context_processors.tz",
148- "django.contrib.messages.context_processors.messages",
149- "maasserver.context_processors.yui",
150- "maasserver.context_processors.global_options",
151-)
152+TEMPLATES = [
153+ {
154+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
155+ 'DIRS': [
156+ os.path.join(os.path.dirname(__file__), "templates"),
157+ ],
158+ 'APP_DIRS': True,
159+ 'OPTIONS': {
160+ 'context_processors': [
161+ # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this
162+ # list if you haven't customized them:
163+ "django.contrib.auth.context_processors.auth",
164+ "django.template.context_processors.debug",
165+ "django.template.context_processors.i18n",
166+ "django.template.context_processors.media",
167+ "django.template.context_processors.request",
168+ "django.template.context_processors.static",
169+ # "django.template.context_processors.tz",
170+ "django.contrib.messages.context_processors.messages",
171+ "maasserver.context_processors.yui",
172+ "maasserver.context_processors.global_options",
173+ ],
174+ },
175+ },
176+]
177
178 MIDDLEWARE_CLASSES = (
179
180@@ -265,14 +271,6 @@
181
182 ROOT_URLCONF = 'maasserver.djangosettings.urls'
183
184-TEMPLATE_DIRS = (
185- # Put strings here, like "/home/html/django_templates"
186- # or "C:/www/django/templates".
187- # Always use forward slashes, even on Windows.
188- # Don't forget to use absolute paths, not relative paths.
189- os.path.join(os.path.dirname(__file__), "templates"),
190-)
191-
192 SOUTH_MIGRATION_MODULES = {
193 # Migrations before MAAS 2.0 are located in south sub-directory.
194 'maasserver': 'maasserver.migrations.south.migrations',
195@@ -350,6 +348,10 @@
196 'maasjson': 'maasserver.json',
197 }
198
199+# MAAS has no upload limit to allow for big image files.
200+# (Django 1.10 introduced this limit with a default of 2.5MB.)
201+DATA_UPLOAD_MAX_MEMORY_SIZE = None
202+
203 # Patch the get_script_prefix method to allow twisted to work with django.
204 patch_get_script_prefix()
205
206
207=== modified file 'src/maasserver/forms/__init__.py'
208--- src/maasserver/forms/__init__.py 2017-06-13 11:27:29 +0000
209+++ src/maasserver/forms/__init__.py 2017-06-16 07:52:25 +0000
210@@ -1512,7 +1512,6 @@
211 name='main_archive',
212 defaults={
213 'url': self.cleaned_data['main_archive'],
214- 'description': 'main_archive',
215 'arches': PackageRepository.MAIN_ARCHES,
216 'default': True,
217 'enabled': True})
218@@ -1520,7 +1519,6 @@
219 name='ports_archive',
220 defaults={
221 'url': self.cleaned_data['ports_archive'],
222- 'description': 'ports_archive',
223 'arches': PackageRepository.PORTS_ARCHES,
224 'default': True,
225 'enabled': True})
226
227=== modified file 'src/maasserver/models/largefile.py'
228--- src/maasserver/models/largefile.py 2016-10-28 15:58:32 +0000
229+++ src/maasserver/models/largefile.py 2017-06-16 07:52:25 +0000
230@@ -149,8 +149,9 @@
231 referencing this object.
232 """
233 links = [
234- rel.get_accessor_name()
235- for rel in self._meta.get_all_related_objects()
236+ f.get_accessor_name() for f in self._meta.get_fields()
237+ if ((f.one_to_many or f.one_to_one) and
238+ f.auto_created and not f.concrete)
239 ]
240 for link in links:
241 if getattr(self, link).exists():
242
243=== modified file 'src/maasserver/models/tests/test_blockdevice.py'
244--- src/maasserver/models/tests/test_blockdevice.py 2017-04-04 21:07:13 +0000
245+++ src/maasserver/models/tests/test_blockdevice.py 2017-06-16 07:52:25 +0000
246@@ -391,25 +391,25 @@
247
248 def test_negative_size(self):
249 node = factory.make_Node()
250- blockdevice = BlockDevice(node=node, name='sda', path='/dev/sda',
251+ blockdevice = BlockDevice(node=node, name='sda',
252 block_size=512, size=-1)
253 self.assertRaises(ValidationError, blockdevice.save)
254
255 def test_minimum_size(self):
256 node = factory.make_Node()
257- blockdevice = BlockDevice(node=node, name='sda', path='/dev/sda',
258+ blockdevice = BlockDevice(node=node, name='sda',
259 block_size=512, size=143359)
260 self.assertRaises(ValidationError, blockdevice.save)
261
262 def test_negative_block_device_size(self):
263 node = factory.make_Node()
264- blockdevice = BlockDevice(node=node, name='sda', path='/dev/sda',
265+ blockdevice = BlockDevice(node=node, name='sda',
266 block_size=-1, size=143360)
267 self.assertRaises(ValidationError, blockdevice.save)
268
269 def test_minimum_block_device_size(self):
270 node = factory.make_Node()
271- blockdevice = BlockDevice(node=node, name='sda', path='/dev/sda',
272+ blockdevice = BlockDevice(node=node, name='sda',
273 block_size=511, size=143360)
274 self.assertRaises(ValidationError, blockdevice.save)
275
276
277=== modified file 'src/maasserver/models/tests/test_physicalblockdevice.py'
278--- src/maasserver/models/tests/test_physicalblockdevice.py 2015-12-01 18:12:59 +0000
279+++ src/maasserver/models/tests/test_physicalblockdevice.py 2017-06-16 07:52:25 +0000
280@@ -20,33 +20,31 @@
281 def test_model_serial_and_no_id_path_requirements_should_save(self):
282 node = factory.make_Node()
283 blockdevice = PhysicalBlockDevice(
284- node=node, name='sda',
285- path='/dev/sda', block_size=512,
286- size=MIN_BLOCK_DEVICE_SIZE, model='A2M0003',
287- serial='001')
288+ node=node, name='sda', block_size=512, size=MIN_BLOCK_DEVICE_SIZE,
289+ model='A2M0003', serial='001')
290 # Should work without issue
291 blockdevice.save()
292
293 def test_id_path_and_no_model_serial_requirements_should_save(self):
294 node = factory.make_Node()
295 blockdevice = PhysicalBlockDevice(
296- node=node, name='sda', path='/dev/sda', block_size=512,
297- size=MIN_BLOCK_DEVICE_SIZE, id_path='/dev/disk/by-id/A2M0003-001')
298+ node=node, name='sda', block_size=512, size=MIN_BLOCK_DEVICE_SIZE,
299+ id_path='/dev/disk/by-id/A2M0003-001')
300 # Should work without issue
301 blockdevice.save()
302
303 def test_no_id_path_and_no_serial(self):
304 node = factory.make_Node()
305 blockdevice = PhysicalBlockDevice(
306- node=node, name='sda', path='/dev/sda', block_size=512,
307- size=MIN_BLOCK_DEVICE_SIZE, model='A2M0003')
308+ node=node, name='sda', block_size=512, size=MIN_BLOCK_DEVICE_SIZE,
309+ model='A2M0003')
310 self.assertRaises(ValidationError, blockdevice.save)
311
312 def test_no_id_path_and_no_model(self):
313 node = factory.make_Node()
314 blockdevice = PhysicalBlockDevice(
315- node=node, name='sda', path='/dev/sda', block_size=512,
316- size=MIN_BLOCK_DEVICE_SIZE, serial='001')
317+ node=node, name='sda', block_size=512, size=MIN_BLOCK_DEVICE_SIZE,
318+ serial='001')
319 self.assertRaises(ValidationError, blockdevice.save)
320
321 def test_number_of_physical_devices_for_returns_correct_count(self):
322
323=== modified file 'src/maasserver/urls.py'
324--- src/maasserver/urls.py 2017-06-01 06:47:23 +0000
325+++ src/maasserver/urls.py 2017-06-16 07:52:25 +0000
326@@ -12,6 +12,10 @@
327 )
328 from django.contrib.auth.decorators import user_passes_test
329 from django.http import HttpResponse
330+from maasserver import (
331+ urls_api,
332+ urls_combo,
333+)
334 from maasserver.bootresources import (
335 simplestreams_file_handler,
336 simplestreams_stream_handler,
337@@ -62,7 +66,7 @@
338 # # URLs accessible to anonymous users.
339 # Combo URLs.
340 urlpatterns = [
341- url(r'combo/', include('maasserver.urls_combo'))
342+ url(r'combo/', include(urls_combo))
343 ]
344
345 # Anonymous views.
346@@ -160,7 +164,7 @@
347
348 # API URLs. If old API requested, provide error message directing to new API.
349 urlpatterns += [
350- url(r'^api/2\.0/', include('maasserver.urls_api')),
351+ url(r'^api/2\.0/', include(urls_api)),
352 url(r'^api/version/', lambda request: HttpResponse(
353 content='2.0', content_type="text/plain"), name='api_version'),
354 url(r'^api/1.0/', lambda request: HttpResponse(
355
356=== modified file 'src/maasserver/utils/__init__.py'
357--- src/maasserver/utils/__init__.py 2016-08-31 08:25:07 +0000
358+++ src/maasserver/utils/__init__.py 2017-06-16 07:52:25 +0000
359@@ -86,10 +86,7 @@
360 if not abs_path.endswith('/'):
361 # Add trailing '/' to get urljoin to behave.
362 abs_path = abs_path + '/'
363- # Force prefix to be '' so that Django doesn't use the 'script prefix' (
364- # which might be there or not depending on whether or not the thread local
365- # variable has been initialized).
366- reverse_link = reverse(view_name, prefix='', *args, **kwargs)
367+ reverse_link = reverse(view_name, *args, **kwargs)
368 if reverse_link.startswith('/'):
369 # Drop the leading '/'.
370 reverse_link = reverse_link[1:]
371
372=== modified file 'src/maasserver/utils/tests/test_views.py'
373--- src/maasserver/utils/tests/test_views.py 2017-03-16 16:40:36 +0000
374+++ src/maasserver/utils/tests/test_views.py 2017-06-16 07:52:25 +0000
375@@ -30,7 +30,6 @@
376 from django.core.urlresolvers import get_resolver
377 from django.db import connection
378 from django.http import HttpResponse
379-from django.http.response import REASON_PHRASES
380 from fixtures import FakeLogger
381 from maasserver.testing.factory import factory
382 from maasserver.testing.testcase import (
383@@ -387,7 +386,7 @@
384 self.expectThat(response.status_code, Equals(http.client.CONFLICT))
385 self.expectThat(
386 response.reason_phrase,
387- Equals(REASON_PHRASES[http.client.CONFLICT]))
388+ Equals(http.client.responses[http.client.CONFLICT]))
389
390 def test__get_response_prepare_retry_context_before_each_try(self):
391
392
393=== modified file 'src/maasserver/utils/views.py'
394--- src/maasserver/utils/views.py 2017-03-16 16:40:36 +0000
395+++ src/maasserver/utils/views.py 2017-06-16 07:52:25 +0000
396@@ -117,6 +117,7 @@
397
398 class HttpResponseConflict(MAASDjangoTemplateResponse):
399 status_code = int(http.client.CONFLICT)
400+ reason_phrase = http.client.responses[http.client.CONFLICT]
401
402
403 class WebApplicationHandler(WSGIHandler):
404
405=== modified file 'src/maasserver/views/account.py'
406--- src/maasserver/views/account.py 2017-03-20 09:45:43 +0000
407+++ src/maasserver/views/account.py 2017-06-16 07:52:25 +0000
408@@ -26,8 +26,7 @@
409 HttpResponseRedirect,
410 JsonResponse,
411 )
412-from django.shortcuts import render_to_response
413-from django.template import RequestContext
414+from django.shortcuts import render
415 from maasserver.models import UserProfile
416 from maasserver.models.user import (
417 create_auth_token,
418@@ -70,11 +69,8 @@
419 else:
420 form = LogoutForm()
421
422- return render_to_response(
423- 'maasserver/logout_confirm.html',
424- {'form': form},
425- context_instance=RequestContext(request),
426- )
427+ return render(
428+ request, 'maasserver/logout_confirm.html', {'form': form})
429
430
431 def authenticate(request):
432
433=== modified file 'src/maasserver/views/prefs.py'
434--- src/maasserver/views/prefs.py 2016-09-26 06:36:07 +0000
435+++ src/maasserver/views/prefs.py 2017-06-16 07:52:25 +0000
436@@ -13,9 +13,8 @@
437 from django.core.urlresolvers import reverse
438 from django.shortcuts import (
439 get_object_or_404,
440- render_to_response,
441+ render,
442 )
443-from django.template import RequestContext
444 from django.views.generic import CreateView
445 from maasserver.forms import (
446 ProfileForm,
447@@ -79,10 +78,10 @@
448 if response is not None:
449 return response
450
451- return render_to_response(
452+ return render(
453+ request,
454 'maasserver/prefs.html',
455 {
456 'profile_form': profile_form,
457- 'password_form': password_form,
458- },
459- context_instance=RequestContext(request))
460+ 'password_form': password_form
461+ })
462
463=== modified file 'src/maasserver/views/settings.py'
464--- src/maasserver/views/settings.py 2017-02-23 08:40:22 +0000
465+++ src/maasserver/views/settings.py 2017-06-16 07:52:25 +0000
466@@ -18,9 +18,8 @@
467 from django.http import HttpResponseRedirect
468 from django.shortcuts import (
469 get_object_or_404,
470- render_to_response,
471+ render,
472 )
473-from django.template import RequestContext
474 from django.views.generic import (
475 CreateView,
476 DeleteView,
477@@ -271,7 +270,8 @@
478 for license_key in license_keys:
479 set_license_key_titles(license_key, osystems)
480
481- return render_to_response(
482+ return render(
483+ request,
484 'maasserver/settings.html',
485 {
486 'user_list': user_list,
487@@ -288,5 +288,4 @@
488 'ubuntu_form': ubuntu_form,
489 'windows_form': windows_form,
490 'kernelopts_form': kernelopts_form,
491- },
492- context_instance=RequestContext(request))
493+ })
494
495=== modified file 'src/maasserver/views/tests/test_general.py'
496--- src/maasserver/views/tests/test_general.py 2017-02-17 14:23:04 +0000
497+++ src/maasserver/views/tests/test_general.py 2017-06-16 07:52:25 +0000
498@@ -12,10 +12,11 @@
499 )
500
501 from django.conf import settings
502-from django.conf.urls import patterns
503+from django.conf.urls import url
504 from django.core.exceptions import PermissionDenied
505 from django.http import Http404
506 from django.test.client import RequestFactory
507+from django.views.defaults import server_error
508 from lxml.html import fromstring
509 from maasserver.testing import extract_redirect
510 from maasserver.testing.factory import factory
511@@ -49,10 +50,7 @@
512 def test_500(self):
513 self.client_log_in()
514 from maasserver.urls import urlpatterns
515- urlpatterns += patterns(
516- '',
517- (r'^500/$', 'django.views.defaults.server_error'),
518- )
519+ urlpatterns += [url(r'^500/$', server_error)]
520 response = self.client.get('/500/')
521 doc = fromstring(response.content)
522 self.assertIn(