Merge lp:~blake-rouse/maas/fix-ui-cache into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4492
Proposed branch: lp:~blake-rouse/maas/fix-ui-cache
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 442 lines (+172/-128)
8 files modified
src/maas/settings.py (+0/-1)
src/maasserver/context_processors.py (+3/-75)
src/maasserver/middleware.py (+4/-3)
src/maasserver/templates/maasserver/css-conf.html (+2/-3)
src/maasserver/templates/maasserver/js-conf.html (+12/-12)
src/maasserver/urls_combo.py (+5/-12)
src/maasserver/views/combo.py (+121/-0)
src/maasserver/views/tests/test_combo.py (+25/-22)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-ui-cache
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+277365@code.launchpad.net

Commit message

Fix loading of css and js to bust the cache on upgrade.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

This forces the browser to reload the file when the installed version is updated. Again this still requires the user to reload the page. But a previous branch has landed that does an auto-reload, but again the client needs to reload to get that updated version to perform the auto reload.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Haven't tested it, but looks good to me!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maas/settings.py'
2--- src/maas/settings.py 2015-10-14 18:32:40 +0000
3+++ src/maas/settings.py 2015-11-12 16:50:21 +0000
4@@ -180,7 +180,6 @@
5 "django.contrib.messages.context_processors.messages",
6 "maasserver.context_processors.yui",
7 "maasserver.context_processors.global_options",
8- "maasserver.context_processors.static_resources",
9 )
10
11 MIDDLEWARE_CLASSES = (
12
13=== modified file 'src/maasserver/context_processors.py'
14--- src/maasserver/context_processors.py 2015-11-09 15:09:26 +0000
15+++ src/maasserver/context_processors.py 2015-11-12 16:50:21 +0000
16@@ -14,7 +14,6 @@
17 __metaclass__ = type
18 __all__ = [
19 "global_options",
20- "static_resources",
21 "yui",
22 ]
23
24@@ -33,86 +32,15 @@
25 }
26
27
28-def static_resources(context):
29- return {
30- 'CSS_LIST': [
31- 'css/base.css',
32- 'css/maas-styles.css',
33- ],
34- 'ANGULAR_LIST': [
35- 'js/angular/maas.js',
36- 'js/angular/factories/region.js',
37- 'js/angular/factories/nodes.js',
38- 'js/angular/factories/devices.js',
39- 'js/angular/factories/clusters.js',
40- 'js/angular/factories/zones.js',
41- 'js/angular/factories/general.js',
42- 'js/angular/factories/users.js',
43- 'js/angular/factories/events.js',
44- 'js/angular/factories/tags.js',
45- 'js/angular/factories/subnets.js',
46- 'js/angular/factories/spaces.js',
47- 'js/angular/factories/vlans.js',
48- 'js/angular/factories/fabrics.js',
49- 'js/angular/services/search.js',
50- 'js/angular/services/manager.js',
51- 'js/angular/services/managerhelper.js',
52- 'js/angular/services/error.js',
53- 'js/angular/services/validation.js',
54- 'js/angular/services/browser.js',
55- 'js/angular/services/converter.js',
56- 'js/angular/services/json.js',
57- 'js/angular/directives/error_overlay.js',
58- 'js/angular/directives/code_lines.js',
59- 'js/angular/directives/error_toggle.js',
60- 'js/angular/directives/call_to_action.js',
61- 'js/angular/directives/power_parameters.js',
62- 'js/angular/directives/os_select.js',
63- 'js/angular/directives/type.js',
64- 'js/angular/directives/accordion.js',
65- 'js/angular/directives/dbl_click_overlay.js',
66- 'js/angular/directives/contenteditable.js',
67- 'js/angular/directives/sticky_header.js',
68- 'js/angular/directives/placeholder.js',
69- 'js/angular/directives/enter_blur.js',
70- 'js/angular/directives/version_reloader.js',
71- 'js/angular/filters/nodes.js',
72- 'js/angular/filters/by_fabric.js',
73- 'js/angular/filters/by_vlan.js',
74- 'js/angular/filters/by_space.js',
75- 'js/angular/filters/remove_default_vlan.js',
76- 'js/angular/controllers/error.js',
77- 'js/angular/controllers/nodes_list.js',
78- 'js/angular/controllers/add_hardware.js',
79- 'js/angular/controllers/add_device.js',
80- 'js/angular/controllers/node_details.js',
81- 'js/angular/controllers/node_details_networking.js',
82- 'js/angular/controllers/node_details_storage.js',
83- 'js/angular/controllers/node_result.js',
84- 'js/angular/controllers/node_events.js',
85- 'js/angular/controllers/subnets_list.js',
86- 'js/angular/controllers/subnet_details.js',
87- ],
88- 'JS_LIST': [
89- 'js/image.js',
90- 'js/image_views.js',
91- 'js/user_panel.js',
92- 'js/prefs.js',
93- 'js/shortpoll.js',
94- 'js/enums.js',
95- 'js/reveal.js',
96- 'js/os_distro_select.js',
97- ],
98- }
99-
100-
101 def global_options(context):
102+ version = get_maas_version_ui()
103 return {
104 'persistent_errors': get_persistent_errors(),
105 'global_options': {
106 'site_name': Config.objects.get_config('maas_name'),
107 },
108 'debug': settings.DEBUG,
109- 'version': get_maas_version_ui(),
110+ 'version': version,
111+ 'files_version': version.replace(" ", ""),
112 'doc_version': get_maas_doc_version(),
113 }
114
115=== modified file 'src/maasserver/middleware.py'
116--- src/maasserver/middleware.py 2015-10-19 04:54:14 +0000
117+++ src/maasserver/middleware.py 2015-11-12 16:50:21 +0000
118@@ -59,6 +59,7 @@
119 from maasserver.models.nodegroup import NodeGroup
120 from maasserver.rpc import getAllClients
121 from maasserver.utils.orm import is_serialization_failure
122+from maasserver.views.combo import MERGE_VIEWS
123 from provisioningserver.rpc.exceptions import (
124 NoConnectionsAvailable,
125 PowerActionAlreadyInProgress,
126@@ -96,9 +97,6 @@
127 reverse('login'),
128 # The combo loaders are publicly accessible.
129 reverse('combo-yui'),
130- reverse('combo-maas'),
131- reverse('combo-jquery'),
132- reverse('combo-angularjs'),
133 # Static resources are publicly visible.
134 settings.STATIC_URL_PATTERN,
135 reverse('robots'),
136@@ -112,6 +110,9 @@
137 # API calls are protected by piston.
138 settings.API_URL_REGEXP,
139 ]
140+ # Add the defined combo loaders.
141+ for filename in MERGE_VIEWS.keys():
142+ public_url_roots.append(reverse('merge', args=[filename]))
143 self.public_urls = re.compile("|".join(public_url_roots))
144 self.login_url = reverse('login')
145
146
147=== modified file 'src/maasserver/templates/maasserver/css-conf.html'
148--- src/maasserver/templates/maasserver/css-conf.html 2015-02-20 17:06:18 +0000
149+++ src/maasserver/templates/maasserver/css-conf.html 2015-11-12 16:50:21 +0000
150@@ -1,3 +1,2 @@
151-{% for css_file in CSS_LIST %}
152- <link rel="stylesheet" href="{{ STATIC_URL }}{{ css_file }}" />
153-{% endfor %}
154+<link rel="stylesheet" href="{{ STATIC_URL }}css/base.css?v={{files_version}}" />
155+<link rel="stylesheet" href="{{ STATIC_URL }}css/maas-styles.css?v={{files_version}}" />
156
157=== modified file 'src/maasserver/templates/maasserver/js-conf.html'
158--- src/maasserver/templates/maasserver/js-conf.html 2015-05-27 14:35:56 +0000
159+++ src/maasserver/templates/maasserver/js-conf.html 2015-11-12 16:50:21 +0000
160@@ -1,14 +1,14 @@
161 <script type="text/javascript"
162- src="{% url "combo-jquery" %}?jquery.min.js">
163-</script>
164-<script type="text/javascript"
165- src="{% url "combo-angularjs" %}?angular.min.js&angular-route.min.js&angular-cookies.min.js">
166-</script>
167-<script type="text/javascript"
168- src="{% url "combo-maas" %}?js/angular/3rdparty/ng-tags-input.js">
169-</script>
170-<script type="text/javascript"
171- src="{% url "combo-maas" %}?{{ ANGULAR_LIST|join:"&" }}">
172+ src="{% url "merge" filename="jquery.js" %}?v={{files_version}}">
173+</script>
174+<script type="text/javascript"
175+ src="{% url "merge" filename="angular.js" %}?v={{files_version}}">
176+</script>
177+<script type="text/javascript"
178+ src="{% url "merge" filename="ng-tags-input.js" %}?v={{files_version}}">
179+</script>
180+<script type="text/javascript"
181+ src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}">
182 </script>
183
184 <script type="text/javascript">
185@@ -35,8 +35,8 @@
186 // -->
187 </script>
188 <script type="text/javascript"
189- src="{% url "combo-yui" %}?yui-base/yui-base-min.js">
190+ src="{% url "merge" filename="yui.js" %}?v={{files_version}}">
191 </script>
192 <script type="text/javascript"
193- src="{% url "combo-maas" %}?{{ JS_LIST|join:"&" }}">
194+ src="{% url "merge" filename="maas-yui.js" %}?v={{files_version}}">
195 </script>
196
197=== modified file 'src/maasserver/urls_combo.py'
198--- src/maasserver/urls_combo.py 2015-05-07 18:14:38 +0000
199+++ src/maasserver/urls_combo.py 2015-11-12 16:50:21 +0000
200@@ -20,25 +20,18 @@
201 patterns,
202 url,
203 )
204-from maasserver.views.combo import get_combo_view
205+from maasserver.views.combo import (
206+ get_combo_view,
207+ merge_view,
208+)
209
210
211 urlpatterns = patterns(
212 '',
213 url(
214- r'^maas/', get_combo_view(
215- location='', default_redirect=settings.STATIC_URL),
216- name='combo-maas'),
217- url(
218 r'^yui/',
219 get_combo_view(location=settings.YUI_LOCATION),
220 name='combo-yui'),
221 url(
222- r'^jquery/',
223- get_combo_view(location=settings.JQUERY_LOCATION),
224- name='combo-jquery'),
225- url(
226- r'^angularjs/',
227- get_combo_view(location=settings.ANGULARJS_LOCATION),
228- name='combo-angularjs'),
229+ r'^(?P<filename>[^/]*)', merge_view, name='merge'),
230 )
231
232=== modified file 'src/maasserver/views/combo.py'
233--- src/maasserver/views/combo.py 2015-06-10 10:08:45 +0000
234+++ src/maasserver/views/combo.py 2015-11-12 16:50:21 +0000
235@@ -23,6 +23,7 @@
236 combine_files,
237 parse_qs,
238 )
239+from django.conf import settings
240 from django.http import (
241 HttpResponse,
242 HttpResponseBadRequest,
243@@ -32,6 +33,108 @@
244 from maasserver.config import RegionConfiguration
245
246
247+MERGE_VIEWS = {
248+ "jquery.js": {
249+ "location": settings.JQUERY_LOCATION,
250+ "content_type": "text/javascript; charset=UTF-8",
251+ "files": [
252+ "jquery.min.js",
253+ ]
254+ },
255+ "angular.js": {
256+ "location": settings.ANGULARJS_LOCATION,
257+ "content_type": "text/javascript; charset=UTF-8",
258+ "files": [
259+ "angular.min.js",
260+ "angular-route.min.js",
261+ "angular-cookies.min.js",
262+ ]
263+ },
264+ "ng-tags-input.js": {
265+ "content_type": "text/javascript; charset=UTF-8",
266+ "files": [
267+ "js/angular/3rdparty/ng-tags-input.js",
268+ ]
269+ },
270+ "maas-angular.js": {
271+ "content_type": "text/javascript; charset=UTF-8",
272+ "files": [
273+ "js/angular/maas.js",
274+ "js/angular/factories/region.js",
275+ "js/angular/factories/nodes.js",
276+ "js/angular/factories/devices.js",
277+ "js/angular/factories/clusters.js",
278+ "js/angular/factories/zones.js",
279+ "js/angular/factories/general.js",
280+ "js/angular/factories/users.js",
281+ "js/angular/factories/events.js",
282+ "js/angular/factories/tags.js",
283+ "js/angular/factories/subnets.js",
284+ "js/angular/factories/spaces.js",
285+ "js/angular/factories/vlans.js",
286+ "js/angular/factories/fabrics.js",
287+ "js/angular/services/search.js",
288+ "js/angular/services/manager.js",
289+ "js/angular/services/managerhelper.js",
290+ "js/angular/services/error.js",
291+ "js/angular/services/validation.js",
292+ "js/angular/services/browser.js",
293+ "js/angular/services/converter.js",
294+ "js/angular/services/json.js",
295+ "js/angular/directives/error_overlay.js",
296+ "js/angular/directives/code_lines.js",
297+ "js/angular/directives/error_toggle.js",
298+ "js/angular/directives/call_to_action.js",
299+ "js/angular/directives/power_parameters.js",
300+ "js/angular/directives/os_select.js",
301+ "js/angular/directives/type.js",
302+ "js/angular/directives/accordion.js",
303+ "js/angular/directives/dbl_click_overlay.js",
304+ "js/angular/directives/contenteditable.js",
305+ "js/angular/directives/sticky_header.js",
306+ "js/angular/directives/placeholder.js",
307+ "js/angular/directives/enter_blur.js",
308+ "js/angular/directives/version_reloader.js",
309+ "js/angular/filters/nodes.js",
310+ "js/angular/filters/by_fabric.js",
311+ "js/angular/filters/by_vlan.js",
312+ "js/angular/filters/by_space.js",
313+ "js/angular/filters/remove_default_vlan.js",
314+ "js/angular/controllers/nodes_list.js",
315+ "js/angular/controllers/add_hardware.js",
316+ "js/angular/controllers/add_device.js",
317+ "js/angular/controllers/node_details.js",
318+ "js/angular/controllers/node_details_networking.js",
319+ "js/angular/controllers/node_details_storage.js",
320+ "js/angular/controllers/node_result.js",
321+ "js/angular/controllers/node_events.js",
322+ "js/angular/controllers/subnets_list.js",
323+ "js/angular/controllers/subnet_details.js",
324+ ]
325+ },
326+ "yui.js": {
327+ "location": settings.YUI_LOCATION,
328+ "content_type": "text/javascript; charset=UTF-8",
329+ "files": [
330+ "yui-base/yui-base-min.js",
331+ ]
332+ },
333+ "maas-yui.js": {
334+ "content_type": "text/javascript; charset=UTF-8",
335+ "files": [
336+ "js/image.js",
337+ "js/image_views.js",
338+ "js/user_panel.js",
339+ "js/prefs.js",
340+ "js/shortpoll.js",
341+ "js/enums.js",
342+ "js/reveal.js",
343+ "js/os_distro_select.js",
344+ ]
345+ },
346+}
347+
348+
349 def get_absolute_location(location=''):
350 """Return the absolute location of a static resource.
351
352@@ -98,3 +201,21 @@
353 content_type=content_type, status=200, content=content)
354
355 return HttpResponseNotFound()
356+
357+
358+def merge_view(request, filename):
359+ """Merge the `files` from `location` into one file. Return the HTTP
360+ response with `content_type`.
361+ """
362+ merge_info = MERGE_VIEWS.get(filename, None)
363+ if merge_info is None:
364+ return HttpResponseNotFound()
365+ location = merge_info.get("location", None)
366+ if location is None:
367+ location = get_absolute_location()
368+ content = "".join(
369+ [content.decode('utf-8') for content in combine_files(
370+ merge_info["files"], location,
371+ resource_prefix='/', rewrite_urls=True)])
372+ return HttpResponse(
373+ content_type=merge_info["content_type"], status=200, content=content)
374
375=== modified file 'src/maasserver/views/tests/test_combo.py'
376--- src/maasserver/views/tests/test_combo.py 2015-06-10 10:08:45 +0000
377+++ src/maasserver/views/tests/test_combo.py 2015-11-12 16:50:21 +0000
378@@ -18,7 +18,6 @@
379 import httplib
380 import os
381
382-from django.conf import settings
383 from django.core.urlresolvers import reverse
384 from django.test.client import RequestFactory
385 from maasserver import config
386@@ -29,6 +28,7 @@
387 from maasserver.views.combo import (
388 get_absolute_location,
389 get_combo_view,
390+ MERGE_VIEWS,
391 )
392 from maastesting.fixtures import ImportErrorFixture
393
394@@ -152,24 +152,27 @@
395 (httplib.BAD_REQUEST, "Invalid file type requested."),
396 (response.status_code, response.content))
397
398- def test_maas_load_js(self):
399- requested_files = ['js/user_panel.js', 'js/enums.js']
400- url = '%s?%s' % (reverse('combo-maas'), '&'.join(requested_files))
401- response = self.client.get(url)
402- # No sign of a missing js file.
403- self.assertNotIn(CONVOY_MISSING_FILE, response.content)
404-
405- def test_maas_load_css(self):
406- requested_files = ['css/base.css']
407- url = '%s?%s' % (reverse('combo-maas'), '&'.join(requested_files))
408- response = self.client.get(url)
409- # No sign of a missing css file.
410- self.assertNotIn(CONVOY_MISSING_FILE, response.content)
411-
412- def test_maas_load_image(self):
413- img_path = 'img/bg_dots.png'
414- url = '%s?%s' % (reverse('combo-maas'), img_path)
415- response = self.client.get(url)
416- self.assertEqual(
417- '%s%s' % (settings.STATIC_URL, img_path),
418- extract_redirect(response))
419+
420+class TestMergeLoaderView(MAASServerTestCase):
421+ """Test merge loader views."""
422+
423+ def test_loads_all_views_correctly(self):
424+ for filename, merge_info in MERGE_VIEWS.items():
425+ url = reverse('merge', args=[filename])
426+ response = self.client.get(url)
427+ self.assertEquals(
428+ merge_info["content_type"], response['Content-Type'],
429+ "Content-type for %s does not match." % filename)
430+
431+ # Has all required files.
432+ for requested_file in merge_info["files"]:
433+ self.assertIn(
434+ requested_file, response.content.decode("utf-8"))
435+
436+ # No sign of a missing js file.
437+ self.assertNotIn(
438+ CONVOY_MISSING_FILE, response.content.decode("utf-8"))
439+
440+ def test_load_unknown_returns_302_blocked_by_middleware(self):
441+ response = self.client.get(reverse('merge', args=["unknown.js"]))
442+ self.assertEqual(httplib.FOUND, response.status_code)