Merge lp:~milo/linaro-ci-dashboard/linkify-next-loop into lp:linaro-ci-dashboard

Proposed by Milo Casagrande
Status: Merged
Merged at revision: 46
Proposed branch: lp:~milo/linaro-ci-dashboard/linkify-next-loop
Merge into: lp:linaro-ci-dashboard
Diff against target: 440 lines (+127/-77)
22 files modified
dashboard/frontend/android_build/models/android_loop.py (+12/-0)
dashboard/frontend/android_build/templates/android_loop_detail.html (+0/-7)
dashboard/frontend/android_build/tests/test_android_build_clientresponse.py (+1/-2)
dashboard/frontend/android_build/tests/test_android_build_views.py (+2/-0)
dashboard/frontend/android_build/views/android_loop_detail_view.py (+0/-2)
dashboard/frontend/android_textfield_loop/models/android_textfield_loop.py (+13/-0)
dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html (+0/-7)
dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py (+1/-2)
dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py (+1/-2)
dashboard/frontend/integration_loop/models/integration_loop.py (+13/-2)
dashboard/frontend/integration_loop/templates/integration_loop_detail.html (+0/-7)
dashboard/frontend/integration_loop/views/integration_loop_detail_view.py (+0/-2)
dashboard/frontend/kernel_build/models/kernel_loop.py (+12/-0)
dashboard/frontend/kernel_build/templates/kernel_loop_detail.html (+0/-7)
dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py (+1/-2)
dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py (+0/-2)
dashboard/frontend/models/loop.py (+15/-2)
dashboard/frontend/models/textfield_loop.py (+1/-1)
dashboard/frontend/templates/loop_detail.html (+21/-2)
dashboard/frontend/templates/textfield_loop_detail.html (+2/-21)
dashboard/frontend/tests/test_clientresponse.py (+1/-2)
dashboard/frontend/views/loop_detail_view.py (+31/-5)
To merge this branch: bzr merge lp:~milo/linaro-ci-dashboard/linkify-next-loop
Reviewer Review Type Date Requested Status
Stevan Radaković Approve
Review via email: mp+123800@code.launchpad.net

Description of the change

Thsi is a tentative implementation of the final detail of the detail view.
At the moment it works only for the textfield-based views, but if the implementation looks good enough, it can be extended to the others too.

The code added is commented, so should be clear what has been done.
Basically, I resolved this using the @permalink annotation in the model, and retrieving the correct instance from the generic Loop model from the fields.

The same approach might be used also in the JavaScript that creates the link for the 'build' path in the detail view.

To post a comment you must log in.
45. By Milo Casagrande

Fixed problem with link.

Revision history for this message
Stevan Radaković (stevanr) wrote :

Hey Milo, looks nice!

Is there anyway we can move the get_next_loop model in the view tho?
Also, since we don't need any of the other permalink methods except detail and build, let's remove them.
And as we've spoken on IRC, let's remove everything related to the build_path now...

review: Needs Fixing
46. By Milo Casagrande

Added permaling method, simplified templates, removed not necessary ones.

47. By Milo Casagrande

Moved get_next_loop function in the view.

48. By Milo Casagrande

Fixed part of the tests.

Revision history for this message
Stevan Radaković (stevanr) wrote :

Looks good.
Thanks.
Approve +1

review: Approve
49. By Milo Casagrande

Fixed test failing, added note.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dashboard/frontend/android_build/models/android_loop.py'
2--- dashboard/frontend/android_build/models/android_loop.py 2012-09-11 14:34:59 +0000
3+++ dashboard/frontend/android_build/models/android_loop.py 2012-09-12 14:34:19 +0000
4@@ -139,3 +139,15 @@
5 exclude=exclude,
6 capitalize=capitalize,
7 upper=True)
8+
9+ @models.permalink
10+ def get_detail_url(self):
11+ return 'AndroidLoopDetail', (), {'slug': self.name}
12+
13+ @models.permalink
14+ def get_build_url(self):
15+ return 'AndroidLoopBuild', (), {'slug': self.name}
16+
17+ @models.permalink
18+ def get_update_url(self):
19+ return 'AndroidLoopUpdate', (), {'slug': self.name}
20
21=== removed file 'dashboard/frontend/android_build/templates/android_loop_detail.html'
22--- dashboard/frontend/android_build/templates/android_loop_detail.html 2012-09-07 15:36:09 +0000
23+++ dashboard/frontend/android_build/templates/android_loop_detail.html 1970-01-01 00:00:00 +0000
24@@ -1,7 +0,0 @@
25-{% extends "loop_detail.html" %}
26-{% block scripts %}
27-{{ block.super }}
28-$("#update_button").click(function() {
29- window.location.href = "{% url AndroidLoopUpdate loop_detail.name %}";
30-});
31-{% endblock scripts %}
32
33=== modified file 'dashboard/frontend/android_build/tests/test_android_build_clientresponse.py'
34--- dashboard/frontend/android_build/tests/test_android_build_clientresponse.py 2012-09-10 08:04:06 +0000
35+++ dashboard/frontend/android_build/tests/test_android_build_clientresponse.py 2012-09-12 14:34:19 +0000
36@@ -66,8 +66,7 @@
37 # Assert template names for detail page.
38 template_names = [template.name for template in
39 response.templates]
40- self.assertEquals(template_names, ["android_loop_detail.html",
41- "loop_detail.html",
42+ self.assertEquals(template_names, ["loop_detail.html",
43 "base.html",
44 "login.html",
45 ])
46
47=== modified file 'dashboard/frontend/android_build/tests/test_android_build_views.py'
48--- dashboard/frontend/android_build/tests/test_android_build_views.py 2012-09-10 08:04:06 +0000
49+++ dashboard/frontend/android_build/tests/test_android_build_views.py 2012-09-12 14:34:19 +0000
50@@ -33,6 +33,8 @@
51 self.mock_loop.loop_ptr.id = 0
52 self.mock_loop.loop_ptr.loopbuild_set = MagicMock()
53 self.mock_loop.loop_ptr.loopbuild_set.all = MagicMock()
54+ self.mock_loop.next_loop = MagicMock()
55+ self.mock_loop.next_loop.id = 1
56
57 def test_create_view_get_context_data(self):
58 android_loop_create_view = AndroidLoopCreateView()
59
60=== modified file 'dashboard/frontend/android_build/views/android_loop_detail_view.py'
61--- dashboard/frontend/android_build/views/android_loop_detail_view.py 2012-09-07 15:08:52 +0000
62+++ dashboard/frontend/android_build/views/android_loop_detail_view.py 2012-09-12 14:34:19 +0000
63@@ -22,5 +22,3 @@
64 class AndroidLoopDetailView(LoopDetailView):
65
66 model = AndroidLoop
67- template_name = "android_loop_detail.html"
68- build_path = 'android'
69
70=== modified file 'dashboard/frontend/android_textfield_loop/models/android_textfield_loop.py'
71--- dashboard/frontend/android_textfield_loop/models/android_textfield_loop.py 2012-09-07 12:49:10 +0000
72+++ dashboard/frontend/android_textfield_loop/models/android_textfield_loop.py 2012-09-12 14:34:19 +0000
73@@ -17,6 +17,7 @@
74
75 from frontend.models.textfield_loop import TextFieldLoop
76 from frontend.models.loop_reference import ANDROID_LOOP
77+from django.db.models import permalink
78
79
80 class AndroidTextFieldLoop(TextFieldLoop):
81@@ -34,3 +35,15 @@
82 def can_chain_into():
83 chain = [ANDROID_LOOP]
84 return chain
85+
86+ @permalink
87+ def get_detail_url(self):
88+ return 'AndroidTextFieldLoopDetail', (), {'slug': self.name}
89+
90+ @permalink
91+ def get_build_url(self):
92+ return 'AndroidTextFieldLoopBuild', (), {'slug': self.name}
93+
94+ @permalink
95+ def get_update_url(self):
96+ return 'AndroidTextFieldLoopUpdate', (), {'slug': self.name}
97
98=== removed file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html'
99--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html 2012-09-10 13:30:37 +0000
100+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html 1970-01-01 00:00:00 +0000
101@@ -1,7 +0,0 @@
102-{% extends "textfield_loop_detail.html" %}
103-{% block scripts %}
104-{{ block.super }}
105-$("#update_button").click(function() {
106- window.location.href = "{% url AndroidTextFieldLoopUpdate loop_detail.name %}";
107-});
108-{% endblock scripts %}
109
110=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py'
111--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py 2012-09-10 13:30:37 +0000
112+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py 2012-09-12 14:34:19 +0000
113@@ -65,8 +65,7 @@
114 template_names = [template.name for template in
115 response.templates]
116 self.assertEquals(template_names,
117- ["android_textfield_loop_detail.html",
118- "textfield_loop_detail.html",
119+ ["textfield_loop_detail.html",
120 "loop_detail.html",
121 "base.html",
122 "login.html",
123
124=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py'
125--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py 2012-09-10 13:30:37 +0000
126+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py 2012-09-12 14:34:19 +0000
127@@ -23,5 +23,4 @@
128 class AndroidTextFieldLoopDetailView(LoopDetailView):
129
130 model = AndroidTextFieldLoop
131- template_name = 'android_textfield_loop_detail.html'
132- build_path = 'android-text'
133+ template_name = 'textfield_loop_detail.html'
134
135=== modified file 'dashboard/frontend/integration_loop/models/integration_loop.py'
136--- dashboard/frontend/integration_loop/models/integration_loop.py 2012-08-31 13:34:41 +0000
137+++ dashboard/frontend/integration_loop/models/integration_loop.py 2012-09-12 14:34:19 +0000
138@@ -42,5 +42,16 @@
139 pass
140
141 def __repr__(self):
142- return "IntegrationLoop(id=%d, branch=%s, ...)" % (self.id,
143- self.branch)
144+ return "IntegrationLoop(id=%d, branch=%s)" % (self.id, self.branch)
145+
146+ @models.permalink
147+ def get_detail_url(self):
148+ return 'IntegrationLoopDetail', (), {'slug': self.name}
149+
150+ @models.permalink
151+ def get_build_url(self):
152+ return 'IntegrationLoopBuild', (), {'slug': self.name}
153+
154+ @models.permalink
155+ def get_update_url(self):
156+ return 'IntegrationLoopUpdate', (), {'slug': self.name}
157
158=== removed file 'dashboard/frontend/integration_loop/templates/integration_loop_detail.html'
159--- dashboard/frontend/integration_loop/templates/integration_loop_detail.html 2012-09-07 15:40:57 +0000
160+++ dashboard/frontend/integration_loop/templates/integration_loop_detail.html 1970-01-01 00:00:00 +0000
161@@ -1,7 +0,0 @@
162-{% extends "loop_detail.html" %}
163-{% block scripts %}
164-{{ block.super }}
165-$("#update_button").click(function() {
166- window.location.href = "{% url IntegrationLoopUpdate loop_detail.name %}";
167-});
168-{% endblock scripts %}
169
170=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_detail_view.py'
171--- dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-09-07 15:40:57 +0000
172+++ dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-09-12 14:34:19 +0000
173@@ -22,5 +22,3 @@
174 class IntegrationLoopDetailView(LoopDetailView):
175
176 model = IntegrationLoop
177- template_name = "integration_loop_detail.html"
178- build_path = 'integration_loop'
179
180=== modified file 'dashboard/frontend/kernel_build/models/kernel_loop.py'
181--- dashboard/frontend/kernel_build/models/kernel_loop.py 2012-08-30 21:49:13 +0000
182+++ dashboard/frontend/kernel_build/models/kernel_loop.py 2012-09-12 14:34:19 +0000
183@@ -123,3 +123,15 @@
184 except:
185 # TODO: Log error.
186 pass
187+
188+ @models.permalink
189+ def get_detail_url(self):
190+ return 'KernelLoopDetail', (), {'slug': self.name}
191+
192+ @models.permalink
193+ def get_build_url(self):
194+ return 'KernelLoopBuild', (), {'slug': self.name}
195+
196+ @models.permalink
197+ def get_update_url(self):
198+ return 'KernelLoopUpdate', (), {'slug': self.name}
199
200=== removed file 'dashboard/frontend/kernel_build/templates/kernel_loop_detail.html'
201--- dashboard/frontend/kernel_build/templates/kernel_loop_detail.html 2012-09-11 12:36:18 +0000
202+++ dashboard/frontend/kernel_build/templates/kernel_loop_detail.html 1970-01-01 00:00:00 +0000
203@@ -1,7 +0,0 @@
204-{% extends "loop_detail.html" %}
205-{% block scripts %}
206-{{ block.super }}
207-$("#update_button").click(function() {
208- window.location.href = "{% url KernelLoopUpdate loop_detail.name %}";
209-});
210-{% endblock scripts %}
211
212=== modified file 'dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py'
213--- dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py 2012-09-10 07:43:51 +0000
214+++ dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py 2012-09-12 14:34:19 +0000
215@@ -70,8 +70,7 @@
216 # Assert template names for detail page.
217 template_names = [template.name for template in
218 response.templates]
219- self.assertEquals(template_names, ["kernel_loop_detail.html",
220- "loop_detail.html",
221+ self.assertEquals(template_names, ["loop_detail.html",
222 "base.html",
223 "login.html",
224 ])
225
226=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py'
227--- dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py 2012-09-07 15:43:36 +0000
228+++ dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py 2012-09-12 14:34:19 +0000
229@@ -22,5 +22,3 @@
230 class KernelLoopDetailView(LoopDetailView):
231
232 model = KernelLoop
233- template_name = 'kernel_loop_detail.html'
234- build_path = 'kernel'
235
236=== modified file 'dashboard/frontend/models/loop.py'
237--- dashboard/frontend/models/loop.py 2012-09-11 14:34:59 +0000
238+++ dashboard/frontend/models/loop.py 2012-09-12 14:34:19 +0000
239@@ -39,7 +39,7 @@
240 next_loop = models.OneToOneField('self', blank=True, null=True,
241 unique=True, on_delete=models.SET_NULL,
242 related_name='previous_loop',
243- verbose_name='Next loop in chain')
244+ verbose_name='Next loop')
245
246 def __init__(self, *args, **kwargs):
247 self.log = Logger.getClassLogger(self)
248@@ -237,7 +237,8 @@
249
250 :return A list of tuples.
251 """
252- excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr']
253+ excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr',
254+ 'next_loop']
255 return self._get_wanted_fields(exclude=excluded_fields,
256 verbose_name=True,
257 capitalize=True)
258@@ -259,3 +260,15 @@
259 This method should be overridden by subclasses.
260 """
261 return configuration
262+
263+ @models.permalink
264+ def get_detail_url(self):
265+ return ()
266+
267+ @models.permalink
268+ def get_build_url(self):
269+ return ()
270+
271+ @models.permalink
272+ def get_update_url(self):
273+ return ()
274
275=== modified file 'dashboard/frontend/models/textfield_loop.py'
276--- dashboard/frontend/models/textfield_loop.py 2012-09-11 07:29:13 +0000
277+++ dashboard/frontend/models/textfield_loop.py 2012-09-12 14:34:19 +0000
278@@ -155,7 +155,7 @@
279 # and define a separate functions to retrieve the lines from the
280 # HTML template.
281 excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr',
282- 'values']
283+ 'values', 'next_loop']
284 return self._get_wanted_fields(exclude=excluded_fields,
285 verbose_name=True,
286 capitalize=True)
287
288=== modified file 'dashboard/frontend/templates/loop_detail.html'
289--- dashboard/frontend/templates/loop_detail.html 2012-09-10 13:30:37 +0000
290+++ dashboard/frontend/templates/loop_detail.html 2012-09-12 14:34:19 +0000
291@@ -9,7 +9,23 @@
292 {% for name, value in loop_detail.get_details %}
293 <div>{{ name }}&#58;&nbsp;{{ value }}</div>
294 {% endfor %}
295+ {% for verbose_name, name, obj in next_loop %}
296+ <div>
297+ {% if name == None %}
298+ {{ verbose_name }}&#58;&nbsp;{{ name }}
299+ {% else %}
300+ {{ verbose_name }}&#58;&nbsp;<a href="{{ obj.get_detail_url }}">{{ name }}</a>
301+ {% endif %}
302+ </div>
303+ {% endfor %}
304 </div>
305+ {% block values %}
306+ {% comment %}
307+ This is used in the textfield detail views, to give a spece
308+ to represent the key<>value values inserte by the user in a better
309+ way.
310+ {% endcomment %}
311+ {% endblock values %}
312 <div>
313 <button type="button" id="update_button">Update loop configuration</button>
314 </div>
315@@ -25,8 +41,11 @@
316 {% endblock content %}
317 {% block scripts %}
318 $("#schedule_button").click(function() {
319- $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/{{ build_path }}/build/{{ loop_detail.name }}/", function(data) {
320+ $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/{{ loop_detail.get_build_url }}", function(data) {
321 $(data).prependTo($("#builds"));
322 });
323 });
324-{% endblock scripts %}
325\ No newline at end of file
326+$("#update_button").click(function() {
327+ window.location.href = "{{ loop_detail.get_update_url }}";
328+});
329+{% endblock scripts %}
330
331=== modified file 'dashboard/frontend/templates/textfield_loop_detail.html'
332--- dashboard/frontend/templates/textfield_loop_detail.html 2012-09-10 13:30:37 +0000
333+++ dashboard/frontend/templates/textfield_loop_detail.html 2012-09-12 14:34:19 +0000
334@@ -1,28 +1,9 @@
335 {% extends "loop_detail.html" %}
336-{% block content %}
337- <div class="header"><h2>Loop {{ loop_detail.name }}</h2></div>
338- <div class="header"><h4>Details</h4></div>
339- <div>
340- {% for name, value in loop_detail.get_details %}
341- <div>{{ name }}&#58;&nbsp;{{ value }}</div>
342- {% endfor %}
343- </div>
344+{% block values %}
345 <div class="header"><h4>Values</h4></div>
346 <div class="textfield">
347 {% for value in loop_detail.get_values %}
348 <div>{{ value }}</div>
349 {% endfor %}
350 </div>
351- <div>
352- <button type="button" id="update_button">Update loop configuration</button>
353- </div>
354- <div class="header"><h4>Builds</h4></div>
355- <div id="builds">
356- {% for build in builds %}
357- {% include "build.html" %}
358- {% endfor %}
359- </div>
360- <div>
361- <button type="button" id="schedule_button">Schedule new build</button>
362- </div>
363-{% endblock content %}
364+{% endblock values %}
365
366=== modified file 'dashboard/frontend/tests/test_clientresponse.py'
367--- dashboard/frontend/tests/test_clientresponse.py 2012-09-10 08:21:41 +0000
368+++ dashboard/frontend/tests/test_clientresponse.py 2012-09-12 14:34:19 +0000
369@@ -108,8 +108,7 @@
370 # Assert template names for detail page.
371 template_names = [template.name for template in
372 response.templates]
373- self.assertEquals(template_names, ["integration_loop_detail.html",
374- "loop_detail.html",
375+ self.assertEquals(template_names, ["loop_detail.html",
376 "base.html",
377 "login.html",
378 ])
379
380=== modified file 'dashboard/frontend/views/loop_detail_view.py'
381--- dashboard/frontend/views/loop_detail_view.py 2012-09-10 07:36:24 +0000
382+++ dashboard/frontend/views/loop_detail_view.py 2012-09-12 14:34:19 +0000
383@@ -20,6 +20,7 @@
384 from django.utils.decorators import method_decorator
385 from frontend.models.loop import Loop
386
387+
388 class LoopDetailView(DetailView):
389
390 # These two fields should *never* be overridden.
391@@ -28,19 +29,44 @@
392 # The following fields have to be overridden in each sub-class.
393 model = Loop
394 template_name = 'loop_detail.html'
395- # This is necessary for each loop to define the URL path for the build
396- # view. This must be overridden.
397- build_path = 'loop'
398
399 @method_decorator(login_required)
400 def dispatch(self, *args, **kwargs):
401 return super(LoopDetailView, self).dispatch(*args, **kwargs)
402
403 def get_context_data(self, **kwargs):
404- '''Get the context data passed to template.'''
405+ """Get the context data passed to template."""
406 context = super(LoopDetailView, self).get_context_data(**kwargs)
407 loop_pointer = self.object.loop_ptr
408 context['request'] = self.request
409 context['builds'] = loop_pointer.loopbuild_set.all()
410- context['build_path'] = self.build_path
411+ context['next_loop'] = self._get_next_loop()
412 return context
413+
414+ def _get_next_loop(self):
415+ """
416+ Retrieves all the necessary information to visualize the next loop as
417+ a link.
418+
419+ :return A list with a three-element tuple containing the verbose name
420+ of the field, the name of the pointed loop, and the actual instance of
421+ the pointed loop.
422+ """
423+ field = self.object._meta.get_field('next_loop')
424+ pk = self.object.next_loop_id
425+ verbose_name = field.verbose_name.capitalize()
426+ # XXX changed to isinstance, since tests were failing: we need to
427+ # mock Loop, not the model variable, in a better way in our tests.
428+ if isinstance(pk, int):
429+ # Get the pointer to the object we want to retrieve, and retrieve
430+ # the actual instance. We can do this since all our loops inherits
431+ # from the same base class (Loop), and we have the information
432+ # about the loop type (in type). Django has a feature to retrieve
433+ # the correct object using the lower-case version of the class
434+ # name.
435+ ptr = Loop.objects.get(pk=pk)
436+ obj = getattr(ptr, ptr.type.lower())
437+ other_loop = [(verbose_name, ptr.name, obj)]
438+ else:
439+ other_loop = [(verbose_name, None, None)]
440+ return other_loop

Subscribers

People subscribed via source and target branches