Merge lp:~rvb/maas/images-link-bug-1427469 into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 3625
Proposed branch: lp:~rvb/maas/images-link-bug-1427469
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 210 lines (+89/-8)
4 files modified
src/maasserver/bootresources.py (+7/-4)
src/maasserver/tests/test_bootresources.py (+15/-3)
src/maasserver/utils/__init__.py (+37/-1)
src/maasserver/utils/tests/test_utils.py (+30/-0)
To merge this branch: bzr merge lp:~rvb/maas/images-link-bug-1427469
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+251934@code.launchpad.net

Commit message

Fix link to the images page: the call to Django's reverse() method isn't done in the context of a request and thus Django can't automatically add the 'script prefix' (i.e. the path prefix) when computing the URL.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good, but do you foresee this as a common problem?

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> Looks good, but do you foresee this as a common problem?

If we use reverse() outside of a Django context (i.e. a request context) then yes, we will need to use the same trick. That being said, the Twisted code is rarely in charge of doing things that require calling reverse()… that's why I decided to fix this quickly instead of creating a proper utility. If and when we see this pattern being repeated, we will refactor this into a utility.

Revision history for this message
ubuntudotcom1 (ubuntudotcom1) wrote :

hmm.. so
abs_path = urlparse("http://localhost:5240/MAAS").path
absolute_reverse('images', base_url=abs_path)

should return '/MAAS/images' correct?

Revision history for this message
Raphaël Badin (rvb) wrote :

Right, there was something fishy due to how urljoin works and how Django silently adds the script prefix when you call reverse() in a Django context.

I've fixed the problem (by adding a dedicated utility) now, please have another look.

Revision history for this message
Gavin Panella (allenap) wrote :

Still looks good, but I have a few questions this time.

review: Approve
Revision history for this message
Raphaël Badin (rvb) :
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (80.5 KiB)

The attempt to merge lp:~rvb/maas/images-link-bug-1427469 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-iscpy python-jinja2 python-jsonschema python-lockfile python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simple...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/bootresources.py'
2--- src/maasserver/bootresources.py 2015-02-20 16:52:57 +0000
3+++ src/maasserver/bootresources.py 2015-03-09 20:52:18 +0000
4@@ -28,7 +28,6 @@
5 import threading
6 import time
7
8-from django.core.urlresolvers import reverse
9 from django.db import (
10 close_old_connections,
11 connections,
12@@ -63,7 +62,10 @@
13 LargeFile,
14 NodeGroup,
15 )
16-from maasserver.utils import absolute_reverse
17+from maasserver.utils import (
18+ absolute_reverse,
19+ absolute_url_reverse,
20+ )
21 from maasserver.utils.orm import (
22 get_one,
23 transactional,
24@@ -1022,6 +1024,7 @@
25 # If the cluster is on the same machine as the region, it's
26 # possible that the cluster has images and the region does not. We
27 # can provide a better message to the user in this case.
28+ images_link = absolute_url_reverse('images')
29 boot_images_locally = list_boot_images()
30 if len(boot_images_locally) > 0:
31 warning = (
32@@ -1029,11 +1032,11 @@
33 "does not. Nodes will not be able to provision until you "
34 "import boot images into the region. Visit the "
35 "<a href=\"%s\">boot images</a> page to start the "
36- "import." % reverse('images'))
37+ "import." % images_link)
38 else:
39 warning = (
40 "Boot image import process not started. Nodes will not "
41 "be able to provision without boot images. Visit the "
42 "<a href=\"%s\">boot images</a> page to start the "
43- "import." % reverse('images'))
44+ "import." % images_link)
45 register_persistent_error(COMPONENT.IMPORT_PXE_FILES, warning)
46
47=== modified file 'src/maasserver/tests/test_bootresources.py'
48--- src/maasserver/tests/test_bootresources.py 2015-03-03 13:46:14 +0000
49+++ src/maasserver/tests/test_bootresources.py 2015-03-09 20:52:18 +0000
50@@ -30,7 +30,10 @@
51 from django.http import StreamingHttpResponse
52 from django.test.client import Client
53 from fixtures import Fixture
54-from maasserver import bootresources
55+from maasserver import (
56+ bootresources,
57+ utils as utils_module,
58+ )
59 from maasserver.bootresources import (
60 BootResourceStore,
61 download_all_boot_resources,
62@@ -1260,6 +1263,9 @@
63 self.expectThat(kwargs, Equals({}))
64
65 def test__adds_warning_if_boot_images_exists_on_cluster_not_region(self):
66+ abs_path = "/%s/%s/" % (factory.make_string(), factory.make_string())
67+ self.patch(
68+ utils_module.settings, 'DEFAULT_MAAS_URL', 'http://%s' % abs_path)
69 list_boot_images = self.patch(bootresources, "list_boot_images")
70 list_boot_images.return_value = [make_rpc_boot_image()]
71
72@@ -1273,11 +1279,16 @@
73 the region. Visit the <a href="%s">boot images</a> page to start the
74 import.
75 """
76+ images_link = abs_path + 'images/'
77 self.assertEqual(
78- normalise_whitespace(error_expected % reverse('images')),
79+ normalise_whitespace(error_expected % images_link),
80 error_observed)
81
82 def test__adds_warning_if_boot_image_import_not_started(self):
83+ abs_path = "/%s/%s/" % (factory.make_string(), factory.make_string())
84+ self.patch(
85+ utils_module.settings, 'DEFAULT_MAAS_URL', 'http://%s' % abs_path)
86+
87 list_boot_images = self.patch(bootresources, "list_boot_images")
88 list_boot_images.return_value = []
89
90@@ -1290,8 +1301,9 @@
91 provision without boot images. Visit the <a href="%s">boot images</a>
92 page to start the import.
93 """
94+ images_link = abs_path + 'images/'
95 self.assertEqual(
96- normalise_whitespace(error_expected % reverse('images')),
97+ normalise_whitespace(error_expected % images_link),
98 error_observed)
99
100 def test__removes_warning_if_boot_image_process_started(self):
101
102=== modified file 'src/maasserver/utils/__init__.py'
103--- src/maasserver/utils/__init__.py 2015-02-19 21:49:54 +0000
104+++ src/maasserver/utils/__init__.py 2015-03-09 20:52:18 +0000
105@@ -14,6 +14,7 @@
106 __metaclass__ = type
107 __all__ = [
108 'absolute_reverse',
109+ 'absolute_reverse_url',
110 'build_absolute_uri',
111 'find_nodegroup',
112 'get_db_state',
113@@ -28,7 +29,10 @@
114 from functools import wraps
115 import re
116 from urllib import urlencode
117-from urlparse import urljoin
118+from urlparse import (
119+ urljoin,
120+ urlparse,
121+ )
122
123 from django.conf import settings
124 from django.core.urlresolvers import reverse
125@@ -86,6 +90,38 @@
126 return url
127
128
129+def absolute_url_reverse(view_name, query=None, *args, **kwargs):
130+ """Returns the absolute path (i.e. starting with '/') for the given view.
131+
132+ This utility is meant to be used by methods that need to compute URLs but
133+ run outside of Django and thus don't have the 'script prefix' transparently
134+ added the the URL.
135+
136+ :param view_name: Django's view function name/reference or URL pattern
137+ name for which to compute the absolute URL.
138+ :param query: Optional query argument which will be passed down to
139+ urllib.urlencode. The result of that call will be appended to the
140+ resulting url.
141+ :param args: Positional arguments for Django's 'reverse' method.
142+ :param kwargs: Named arguments for Django's 'reverse' method.
143+ """
144+ abs_path = urlparse(settings.DEFAULT_MAAS_URL).path
145+ if not abs_path.endswith('/'):
146+ # Add trailing '/' to get urljoin to behave.
147+ abs_path = abs_path + '/'
148+ # Force prefix to be '' so that Django doesn't use the 'script prefix' (
149+ # which might be there or not depending on whether or not the thread local
150+ # variable has been initialized).
151+ reverse_link = reverse(view_name, prefix='', *args, **kwargs)
152+ if reverse_link.startswith('/'):
153+ # Drop the leading '/'.
154+ reverse_link = reverse_link[1:]
155+ url = urljoin(abs_path, reverse_link)
156+ if query is not None:
157+ url += '?%s' % urlencode(query, doseq=True)
158+ return url
159+
160+
161 def build_absolute_uri(request, path):
162 """Return absolute URI corresponding to given absolute path.
163
164
165=== modified file 'src/maasserver/utils/tests/test_utils.py'
166--- src/maasserver/utils/tests/test_utils.py 2015-02-19 21:49:54 +0000
167+++ src/maasserver/utils/tests/test_utils.py 2015-03-09 20:52:18 +0000
168@@ -32,6 +32,7 @@
169 from maasserver.testing.testcase import MAASServerTestCase
170 from maasserver.utils import (
171 absolute_reverse,
172+ absolute_url_reverse,
173 build_absolute_uri,
174 find_nodegroup,
175 get_db_state,
176@@ -83,6 +84,35 @@
177 self.assertEqual(expected_url, absolute_url)
178
179
180+class TestAbsoluteUrlReverse(MAASServerTestCase):
181+
182+ def test_absolute_url_reverse_uses_path_from_DEFAULT_MAAS_URL(self):
183+ path = "/%s/%s" % (factory.make_string(), factory.make_string())
184+ maas_url = 'http://%s%s' % (factory.make_string(), path)
185+ self.patch(settings, 'DEFAULT_MAAS_URL', maas_url)
186+ absolute_url = absolute_url_reverse('settings')
187+ expected_url = path + reverse('settings')
188+ self.assertEqual(expected_url, absolute_url)
189+
190+ def test_absolute_url_reverse_copes_with_trailing_slash(self):
191+ path = "/%s/%s/" % (factory.make_string(), factory.make_string())
192+ maas_url = 'http://%s%s' % (factory.make_string(), path)
193+ self.patch(settings, 'DEFAULT_MAAS_URL', maas_url)
194+ absolute_url = absolute_url_reverse('settings')
195+ expected_url = path[:-1] + reverse('settings')
196+ self.assertEqual(expected_url, absolute_url)
197+
198+ def test_absolute_url_reverse_uses_query_string(self):
199+ path = "/%s/%s" % (factory.make_string(), factory.make_string())
200+ maas_url = 'http://%s%s' % (factory.make_string(), path)
201+ self.patch(settings, 'DEFAULT_MAAS_URL', maas_url)
202+ parameters = {factory.make_string(): factory.make_string()}
203+ absolute_url = absolute_url_reverse('settings', query=parameters)
204+ expected_url = path + "%s?%s" % (
205+ reverse('settings'), urlencode(parameters))
206+ self.assertEqual(expected_url, absolute_url)
207+
208+
209 class GetDbStateTest(MAASServerTestCase):
210 """Testing for the method `get_db_state`."""
211