Merge lp:~milo/linaro-ci-dashboard/views-refactoring into lp:linaro-ci-dashboard

Proposed by Milo Casagrande
Status: Merged
Approved by: Stevan Radaković
Approved revision: 74
Merged at revision: 43
Proposed branch: lp:~milo/linaro-ci-dashboard/views-refactoring
Merge into: lp:linaro-ci-dashboard
Diff against target: 2356 lines (+807/-787)
56 files modified
HACKING (+60/-0)
dashboard/frontend/android_build/forms/android_loop_form.py (+16/-27)
dashboard/frontend/android_build/models/android_loop.py (+12/-1)
dashboard/frontend/android_build/templates/android_loop_create.html (+6/-23)
dashboard/frontend/android_build/templates/android_loop_detail.html (+4/-45)
dashboard/frontend/android_build/templates/android_loop_update.html (+7/-5)
dashboard/frontend/android_build/tests/test_android_build_clientresponse.py (+2/-0)
dashboard/frontend/android_build/tests/test_android_build_views.py (+0/-3)
dashboard/frontend/android_build/views/android_loop_create_view.py (+5/-30)
dashboard/frontend/android_build/views/android_loop_detail_view.py (+5/-31)
dashboard/frontend/android_build/views/android_loop_update_view.py (+7/-34)
dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py (+7/-17)
dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html (+4/-12)
dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html (+6/-41)
dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html (+4/-6)
dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py (+3/-0)
dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py (+7/-3)
dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py (+0/-4)
dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py (+5/-32)
dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py (+6/-24)
dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py (+7/-35)
dashboard/frontend/forms/loop_form.py (+48/-0)
dashboard/frontend/forms/textfield_loop_form.py (+12/-6)
dashboard/frontend/integration_loop/forms/integration_loop_form.py (+14/-26)
dashboard/frontend/integration_loop/templates/integration_loop_create.html (+3/-7)
dashboard/frontend/integration_loop/templates/integration_loop_detail.html (+4/-51)
dashboard/frontend/integration_loop/templates/integration_loop_update.html (+4/-8)
dashboard/frontend/integration_loop/views/integration_loop_create_view.py (+5/-29)
dashboard/frontend/integration_loop/views/integration_loop_detail_view.py (+5/-21)
dashboard/frontend/integration_loop/views/integration_loop_update_view.py (+7/-34)
dashboard/frontend/kernel_build/forms/kernel_loop_form.py (+14/-25)
dashboard/frontend/kernel_build/templates/kernel_loop_create.html (+3/-7)
dashboard/frontend/kernel_build/templates/kernel_loop_detail.html (+4/-46)
dashboard/frontend/kernel_build/templates/kernel_loop_update.html (+4/-8)
dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py (+2/-0)
dashboard/frontend/kernel_build/views/kernel_loop_create_view.py (+5/-30)
dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py (+6/-22)
dashboard/frontend/kernel_build/views/kernel_loop_update_view.py (+7/-33)
dashboard/frontend/models/loop.py (+104/-12)
dashboard/frontend/models/textfield_loop.py (+48/-6)
dashboard/frontend/templates/base.html (+13/-21)
dashboard/frontend/templates/index.html (+0/-2)
dashboard/frontend/templates/loop_create.html (+34/-0)
dashboard/frontend/templates/loop_detail.html (+32/-0)
dashboard/frontend/templates/textfield_loop_detail.html (+28/-0)
dashboard/frontend/templatetags/dashboard_extras.py (+0/-3)
dashboard/frontend/tests/test_clientresponse.py (+2/-0)
dashboard/frontend/tests/test_models.py (+60/-0)
dashboard/frontend/urls.py (+0/-1)
dashboard/frontend/views/loop_build_view.py (+3/-1)
dashboard/frontend/views/loop_create_view.py (+52/-0)
dashboard/frontend/views/loop_detail_view.py (+46/-0)
dashboard/frontend/views/loop_get_chainable_view.py (+7/-9)
dashboard/frontend/views/loop_update_view.py (+55/-0)
dashboard/frontend/widgets/chain_loop_widget.py (+3/-5)
dashboard/jenkinsserver/models/jenkins_server.py (+0/-1)
To merge this branch: bzr merge lp:~milo/linaro-ci-dashboard/views-refactoring
Reviewer Review Type Date Requested Status
Stevan Radaković Approve
Данило Шеган Pending
Review via email: mp+123489@code.launchpad.net

Description of the change

Here there is all the refactoring work trying to unify the various views with a more hierarchical structure.
The diff here might be really huge!

Work done so far:
- refactored the create, update and detail views of each sub-application, creating a generic super class for each views, and inheriting from it. The generic classes are stored in `frontend/views'.
- created generic HTML templates for the views, to be used as super class always for inheritance. All the generic templates are stored in `frontend/templates'.
- the HTML templates have been created using a "<div>" approach, defining also some CSS class that might be used for visual layout.
- refactored the code where necessary, adding also some tests.

To post a comment you must log in.
Revision history for this message
Milo Casagrande (milo) wrote :

This is moslty all that there is.

For the "build" part, I tried a couple of different approaches, but none really worked. Using even the get_model static method, we need to recunstruct the model from the class, so or we override __init__ or create some custom methods for each loop. I still think that defining for each loop the build URL and a basic build_view is probably better than having the heavily depend on getattr.

There might still be some tweaks to do in the future if we insert ManyToMany fields or other relationship fields in the model and we need to show them, since we will need to traverse the PK and get the real values.

70. By Milo Casagrande

Fixed test regression.

71. By Milo Casagrande

Merged from trunk.

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

Thanks for the extensive refactoring Milo !!1!

Couple of things to fix while I go further through the code:

1. 'Schedule build' does not work on kernel loops (no appropriate javascript handler on the page).
2. 'Next loop in chain' on detail page (at least one, textfield loop) is not a link as suggested, but it just states 'Loop object'
3. 'Schedule build' breaks if the loop has chained loop. This is because the AndroidLoop model has overriden method schedule_build with only one parameter instead of two like the parent model (I know this is not of your doing, at least not in this branch) but this looks like a good place to fix it).

review: Needs Fixing
Revision history for this message
Milo Casagrande (milo) wrote :

On Tue, Sep 11, 2012 at 1:48 PM, Stevan Radaković
<email address hidden> wrote:
>
> 1. 'Schedule build' does not work on kernel loops (no appropriate javascript handler on the page).
> 2. 'Next loop in chain' on detail page (at least one, textfield loop) is not a link as suggested, but it just states 'Loop object'
> 3. 'Schedule build' breaks if the loop has chained loop. This is because the AndroidLoop model has overriden method schedule_build with only one parameter instead of two like the parent model (I know this is not of your doing, at least not in this branch) but this looks like a good place to fix it).

2.5 fixed.
The only missing part is the link part for the chainable loop,
deliberately left out from this, since I wanted to find a better way
to do it (otherwise I will have to check if the value comes from the
"next_loop" field). The "Loop object" thing was a regression, it
should have been the name anyway.

The point 3, was it broken only in AndroidLoop? The others seem to
work for me locally.

--
Milo Casagrande
Infrastructure Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs

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

3. happens when you chain AndroidLoop on the AndroidTextFieldLoop create page. I'll double check if it's something on my side but you can include the proposed fix for now. (it makes sense that other modules work because the broken method definition is in the AndroidLoop model only)

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

Also, didn't we agreed to have the Build detail view page as well as part of this WI? :/

Revision history for this message
Milo Casagrande (milo) wrote :

On Tue, Sep 11, 2012 at 3:00 PM, Stevan Radaković
<email address hidden> wrote:
> Also, didn't we agreed to have the Build detail view page as well as part of this WI? :/

Yep, but I left it out of the refactoring. Will probably split the WI
if I can't land it by today.

--
Milo Casagrande
Infrastructure Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs

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

The code looks great. Two small comments:

1 === modified file 'HACKING'
2 --- HACKING 2012-09-07 10:39:47 +0000
3 +++ HACKING 2012-09-11 07:31:18 +0000
4 @@ -49,6 +49,66 @@
5
6 Tests should be written and defined inside each sub-application.
7
8 +== Class Hierarchy ==
9 +
10 +=== Templates & Views ===
11 +
12 +The `frotend' base application includes the general HTML Django templates that
13 +each sub-application should base itself on. These templates are stored in the
14 +`frontend/templates/' directory.

Small typo on the froNtend application name.

131 raise ValidationError("With build of type 'Android Toolchain "
132 - "Linaro, it is necessary to specify "
133 + "Linaro', it is necessary to specify "
134 "GCC URL.")

I think this here single quote is to necessary because of the first on wich opens up at the 'Android Toolchain Linaro'.
Or the one before Android should be deleted as well.

72. By Milo Casagrande

Fixed kernel build scheduling.

73. By Milo Casagrande

Fixed name and method signature.

74. By Milo Casagrande

Fixed typos.

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

Good to go :)
Approve +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'HACKING'
2--- HACKING 2012-09-07 10:39:47 +0000
3+++ HACKING 2012-09-11 14:48:19 +0000
4@@ -49,6 +49,66 @@
5
6 Tests should be written and defined inside each sub-application.
7
8+== Class Hierarchy ==
9+
10+=== Templates & Views ===
11+
12+The `frontend' base application includes the general HTML Django templates that
13+each sub-application should base itself on. These templates are stored in the
14+`frontend/templates/' directory.
15+
16+The `base.html' template is the basic structures for all the HTML pages in the
17+application.
18+The `login.html' template is the one used for login capabilities.
19+The `home.html' template is empty for the moment.
20+The `list_chainable' template is used for defining chainability of a loop.
21+
22+The templates with `create', `update', `detail' and `build' words in them are
23+used to implement the views of each loop. These are the templates each loop
24+templates should inherit from.
25+
26+This is the inheritance hierarchy for the HTML templates, in this case for the
27+`create' view:
28+
29+base.html
30+ ^
31+ |
32+ ^
33+ |
34+loop_create_view.html
35+ ^
36+ |
37+ ^
38+ |
39+android_build_create_view.html
40+
41+In a sub-application template, if nothing particular is needed, it is only
42+necessary to specify the correct URL name, as defined in each sup-application
43+`urls.py' file.
44+
45+This is the inheritance hierarchy for the Python view classes, always for a
46+`create' view:
47+
48+loop_create_view.py
49+ ^
50+ |
51+ ^
52+ |
53+android_build_create_view.py
54+
55+Based on the view Python class, in each sub-application, if nothing special is
56+needed, it is only necessary to override the following variables:
57+ * template_name
58+ * form_class
59+ * model
60+ * reverse_url or build_path
61+
62+What should never be overridden is:
63+ * context_object_name
64+ * slug_field
65+
66+
67+
68 = Loop Chaining =
69
70 Loop chaining in the linaro-ci-dashboard is a mechanism to use output from one
71
72=== modified file 'dashboard/frontend/android_build/forms/android_loop_form.py'
73--- dashboard/frontend/android_build/forms/android_loop_form.py 2012-09-04 11:23:32 +0000
74+++ dashboard/frontend/android_build/forms/android_loop_form.py 2012-09-11 14:48:19 +0000
75@@ -15,44 +15,33 @@
76 # You should have received a copy of the GNU Affero General Public License
77 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
78
79-from django.forms import CharField
80-from django.forms import IntegerField
81-from django.forms import ModelForm
82-from django.forms.widgets import HiddenInput
83-from django.forms import ValidationError
84+from django.forms import (
85+ CharField,
86+ HiddenInput,
87+ ValidationError
88+ )
89 from frontend.android_build.models.android_loop import (
90 AndroidLoop,
91 BUILD_ANDROID_TOOLCHAIN_LINARO,
92 )
93-from frontend.widgets.chain_loop_widget import ChainLoopWidget
94-
95-
96-class AndroidLoopForm(ModelForm):
97-
98- next_loop = IntegerField(widget=ChainLoopWidget, required=False)
99+from frontend.forms.loop_form import LoopForm
100+
101+
102+class AndroidLoopForm(LoopForm):
103+ """
104+ Form class for the android_build application.
105+ """
106+ class Meta(LoopForm.Meta):
107+ model = AndroidLoop
108+
109 type = CharField(widget=HiddenInput, initial=AndroidLoop.__name__,
110 required=False)
111
112- class Meta:
113- model = AndroidLoop
114- exclude = ('server')
115-
116- def save(self, *args, **kwargs):
117- instance = super(AndroidLoopForm, self).save(commit=False)
118-
119- if self.data["next_loop_0"]:
120- next_loop = Loop.objects.get(id=self.data["next_loop_0"])
121- instance.next_loop = next_loop
122-
123- instance.save()
124-
125- return instance
126-
127 def clean(self):
128 cleaned_data = super(AndroidLoopForm, self).clean()
129 if cleaned_data.get('build_type') == BUILD_ANDROID_TOOLCHAIN_LINARO[0]:
130 if cleaned_data['gcc_url'] == '':
131- raise ValidationError("With build of type 'Android Toolchain "
132+ raise ValidationError("With build of type Android Toolchain "
133 "Linaro, it is necessary to specify "
134 "GCC URL.")
135 return cleaned_data
136
137=== modified file 'dashboard/frontend/android_build/models/android_loop.py'
138--- dashboard/frontend/android_build/models/android_loop.py 2012-08-30 21:49:13 +0000
139+++ dashboard/frontend/android_build/models/android_loop.py 2012-09-11 14:48:19 +0000
140@@ -125,6 +125,17 @@
141 # TODO: Log error.
142 pass
143
144- def schedule_build(self):
145+ def schedule_build(self, parameters=None):
146 parameters = {'parameter': [{'delay': '0'}]}
147 return super(self.__class__, self).schedule_build(parameters)
148+
149+ def base64_config(self,
150+ include=None,
151+ exclude=None,
152+ capitalize=False,
153+ upper=False):
154+ exclude = ['id', 'server', 'loop_ptr', 'next_loop', 'name', 'type']
155+ return super(AndroidLoop, self).base64_config(include=include,
156+ exclude=exclude,
157+ capitalize=capitalize,
158+ upper=True)
159
160=== modified file 'dashboard/frontend/android_build/templates/android_loop_create.html'
161--- dashboard/frontend/android_build/templates/android_loop_create.html 2012-08-31 15:21:11 +0000
162+++ dashboard/frontend/android_build/templates/android_loop_create.html 2012-09-11 14:48:19 +0000
163@@ -1,26 +1,10 @@
164-{% extends "base.html" %}
165-
166-{% block content %}
167-
168-{% block create_form %}
169-<form name="{{ form_name }}" action="{% url AndroidLoopCreate %}" method="post">
170-{% csrf_token %}
171-{% endblock create_form %}
172-
173-{% if form.non_field_errors %}
174- <div class="form_error">
175- {% for err in form.non_field_errors %}
176- <div class="error_message">{{ err }}</div>
177- {% endfor %}
178- </div>
179-{% endif %}
180-{{ form.as_p }}
181- <div><input type="submit" value="Submit" /></div>
182-</form>
183-{% endblock %}
184+{% extends "loop_create.html" %}
185+{% block content_form %}
186+<form action="{% url AndroidLoopCreate %}" method="post">{% csrf_token %}
187+{% endblock content_form %}
188
189 {% block scripts %}
190-<script type="text/javascript">
191+{{ block.super }}
192 $('#id_lava_submit').attr('onclick', 'disable_enable_lava()');
193
194 disable_enable_lava()
195@@ -36,5 +20,4 @@
196 $('#id_lava_submit_fatal').attr('disabled', disable);
197 $('#id_lava_android_binaries').attr('disabled', disable);
198 }
199-</script>
200-{% endblock %}
201+{% endblock scripts %}
202
203=== modified file 'dashboard/frontend/android_build/templates/android_loop_detail.html'
204--- dashboard/frontend/android_build/templates/android_loop_detail.html 2012-08-27 15:14:06 +0000
205+++ dashboard/frontend/android_build/templates/android_loop_detail.html 2012-09-11 14:48:19 +0000
206@@ -1,48 +1,7 @@
207-{% extends "base.html" %}
208-{% load dashboard_extras %}
209-
210-{% block breadcrumbs %}
211-<div><a href="{% url Index %}">Index</a></div>
212-{% endblock %}
213-
214-{% block content %}
215- <h2>Loop {{ android_loop_detail.name }}</h2>
216-
217- <h4>Details</h4>
218- {% for instance in data %}
219- {% for field, value in instance.fields.items %}
220- <div>
221- {{ android_loop_detail|verbose_name:field }}: {{ value }}
222- </div>
223- {% endfor %}
224- {% endfor %}
225-
226- <div>
227- <button type="button" id="update_button">Update loop configuration</button>
228- </div>
229-
230- <h4>Builds</h4>
231- <div id="builds">
232- {% for build in builds %}
233- {% include "build.html" %}
234- {% endfor %}
235- </div>
236-
237- <div>
238- <button type="button" id="schedule_button">Schedule new build</button>
239- </div>
240-{% endblock %}
241-
242+{% extends "loop_detail.html" %}
243 {% block scripts %}
244-<script type="text/javascript">
245-$("#schedule_button").click(function() {
246- $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/android/build/{{ android_loop_detail.name }}/", function(data) {
247- $(data).prependTo($("#builds"));
248- });
249-});
250-
251+{{ block.super }}
252 $("#update_button").click(function() {
253- window.location.href = "{% url AndroidLoopUpdate android_loop_detail.name %}";
254+ window.location.href = "{% url AndroidLoopUpdate loop_detail.name %}";
255 });
256-</script>
257-{% endblock %}
258+{% endblock scripts %}
259
260=== modified file 'dashboard/frontend/android_build/templates/android_loop_update.html'
261--- dashboard/frontend/android_build/templates/android_loop_update.html 2012-08-14 12:07:33 +0000
262+++ dashboard/frontend/android_build/templates/android_loop_update.html 2012-09-11 14:48:19 +0000
263@@ -1,6 +1,8 @@
264 {% extends "android_loop_create.html" %}
265-
266-{% block create_form %}
267- <form name="{{ form_name }}" action="{% url AndroidLoopUpdate android_loop_update.name %}" method="post">
268- {% csrf_token %}
269-{% endblock create_form %}
270\ No newline at end of file
271+{% comment %}
272+We inherit from android_loop_create.html in this case, since we need a small
273+JavaScript that has been written in the android_loop_create.html template.
274+{% endcomment %}
275+{% block content_form %}
276+<form action="{% url AndroidLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
277+{% endblock content_form %}
278
279=== modified file 'dashboard/frontend/android_build/tests/test_android_build_clientresponse.py'
280--- dashboard/frontend/android_build/tests/test_android_build_clientresponse.py 2012-09-04 11:23:32 +0000
281+++ dashboard/frontend/android_build/tests/test_android_build_clientresponse.py 2012-09-11 14:48:19 +0000
282@@ -42,6 +42,7 @@
283 template_names = [template.name for template in
284 response.templates]
285 self.assertEquals(template_names, ["android_loop_create.html",
286+ "loop_create.html",
287 "base.html",
288 "login.html",
289 ])
290@@ -66,6 +67,7 @@
291 template_names = [template.name for template in
292 response.templates]
293 self.assertEquals(template_names, ["android_loop_detail.html",
294+ "loop_detail.html",
295 "base.html",
296 "login.html",
297 ])
298
299=== modified file 'dashboard/frontend/android_build/tests/test_android_build_views.py'
300--- dashboard/frontend/android_build/tests/test_android_build_views.py 2012-08-23 10:17:07 +0000
301+++ dashboard/frontend/android_build/tests/test_android_build_views.py 2012-09-11 14:48:19 +0000
302@@ -41,7 +41,6 @@
303 context = AndroidLoopCreateView.get_context_data(
304 android_loop_create_view)
305 self.assertEqual(context['request'], "some request data")
306- self.assertEqual(context['form_name'], AndroidLoopCreateView.form_name)
307
308 def test_detail_view_get_context_data(self):
309 android_loop_detail_view = AndroidLoopDetailView()
310@@ -51,7 +50,6 @@
311 android_loop_detail_view)
312 self.assertEqual(context['request'], "some request data")
313 self.assertEqual(context['builds'].__class__.__name__, "MagicMock")
314- self.assertEqual(context['data'].__class__.__name__, "list")
315
316 def test_update_view_get_context_data(self):
317 android_loop_update_view = AndroidLoopUpdateView()
318@@ -60,4 +58,3 @@
319 context = AndroidLoopUpdateView.get_context_data(
320 android_loop_update_view)
321 self.assertEqual(context['request'], "some request data")
322- self.assertEqual(context['form_name'], AndroidLoopUpdateView.form_name)
323
324=== modified file 'dashboard/frontend/android_build/views/android_loop_create_view.py'
325--- dashboard/frontend/android_build/views/android_loop_create_view.py 2012-08-17 09:25:34 +0000
326+++ dashboard/frontend/android_build/views/android_loop_create_view.py 2012-09-11 14:48:19 +0000
327@@ -16,39 +16,14 @@
328 # You should have received a copy of the GNU Affero General Public License
329 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
330
331-from django.contrib.auth.decorators import login_required
332-from django.core.urlresolvers import reverse
333-from django.views.generic.edit import CreateView
334-from django.utils.decorators import method_decorator
335 from frontend.android_build.models.android_loop import AndroidLoop
336 from frontend.android_build.forms.android_loop_form import AndroidLoopForm
337-
338-
339-class AndroidLoopCreateView(CreateView):
340+from frontend.views.loop_create_view import LoopCreateView
341+
342+
343+class AndroidLoopCreateView(LoopCreateView):
344
345 template_name = "android_loop_create.html"
346 form_class = AndroidLoopForm
347 model = AndroidLoop
348- form_name = 'android_loop'
349-
350- def form_valid(self, form):
351- """This method is called when valid form data has been POSTed.
352-
353- It should return an HttpResponse."""
354- return super(AndroidLoopCreateView, self).form_valid(form)
355-
356- @method_decorator(login_required)
357- def dispatch(self, *args, **kwargs):
358- return super(AndroidLoopCreateView, self).dispatch(*args, **kwargs)
359-
360- def get_context_data(self, **kwargs):
361- '''Get the context data passed to template.'''
362- context = super(AndroidLoopCreateView,
363- self).get_context_data(**kwargs)
364- context['request'] = self.request
365- context['form_name'] = self.form_name
366- return context
367-
368- def get_success_url(self):
369- '''Return the URL when the form is valid.'''
370- return reverse('AndroidLoopDetail', args=[self.object.name])
371+ reverse_url = 'AndroidLoopDetail'
372
373=== modified file 'dashboard/frontend/android_build/views/android_loop_detail_view.py'
374--- dashboard/frontend/android_build/views/android_loop_detail_view.py 2012-08-24 12:35:00 +0000
375+++ dashboard/frontend/android_build/views/android_loop_detail_view.py 2012-09-11 14:48:19 +0000
376@@ -1,4 +1,3 @@
377-#!/usr/bin/env python
378 # Copyright (C) 2012 Linaro
379 #
380 # This file is part of linaro-ci-dashboard.
381@@ -16,37 +15,12 @@
382 # You should have received a copy of the GNU Affero General Public License
383 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
384
385-from django.contrib.auth.decorators import login_required
386-from django.views.generic.detail import DetailView
387-from django.utils.decorators import method_decorator
388-from frontend.models.loop import Loop
389 from frontend.android_build.models.android_loop import AndroidLoop
390-from django.core import serializers
391-from itertools import chain
392-
393-
394-class AndroidLoopDetailView(DetailView):
395+from frontend.views.loop_detail_view import LoopDetailView
396+
397+
398+class AndroidLoopDetailView(LoopDetailView):
399
400 model = AndroidLoop
401- context_object_name = 'android_loop_detail'
402- slug_field = "name"
403 template_name = "android_loop_detail.html"
404-
405- @method_decorator(login_required)
406- def dispatch(self, *args, **kwargs):
407- return super(AndroidLoopDetailView, self).dispatch(*args, **kwargs)
408-
409- def get_context_data(self, **kwargs):
410- '''Get the context data passed to template.'''
411- context = super(AndroidLoopDetailView,
412- self).get_context_data(**kwargs)
413- context['request'] = self.request
414- context['builds'] = self.object.loop_ptr.loopbuild_set.all()
415-
416- # Need to "merge" the two queryset for all the fields to appear
417- querysets = list(chain(AndroidLoop.objects.filter(
418- pk=self.object.loop_ptr.id),
419- Loop.objects.filter(pk=self.object.loop_ptr.id)))
420- context['data'] = serializers.serialize("python", querysets)
421-
422- return context
423+ build_path = 'android'
424
425=== modified file 'dashboard/frontend/android_build/views/android_loop_update_view.py'
426--- dashboard/frontend/android_build/views/android_loop_update_view.py 2012-08-20 10:25:27 +0000
427+++ dashboard/frontend/android_build/views/android_loop_update_view.py 2012-09-11 14:48:19 +0000
428@@ -15,41 +15,14 @@
429 # You should have received a copy of the GNU Affero General Public License
430 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
431
432-from django.contrib.auth.decorators import login_required
433-from django.core.urlresolvers import reverse
434-from django.views.generic.edit import UpdateView
435-from django.utils.decorators import method_decorator
436 from frontend.android_build.forms.android_loop_form import AndroidLoopForm
437 from frontend.android_build.models.android_loop import AndroidLoop
438-
439-
440-class AndroidLoopUpdateView(UpdateView):
441-
442- template_name = "android_loop_update.html"
443+from frontend.views.loop_update_view import LoopUpdateView
444+
445+
446+class AndroidLoopUpdateView(LoopUpdateView):
447+
448+ template_name = 'android_loop_update.html'
449 form_class = AndroidLoopForm
450- context_object_name = 'android_loop_update'
451 model = AndroidLoop
452- slug_field = "name"
453- form_name = 'android_loop'
454-
455- def form_valid(self, form):
456- """This method is called when valid form data has been POSTed.
457-
458- It should return an HttpResponse."""
459- return super(AndroidLoopUpdateView, self).form_valid(form)
460-
461- @method_decorator(login_required)
462- def dispatch(self, *args, **kwargs):
463- return super(AndroidLoopUpdateView, self).dispatch(*args, **kwargs)
464-
465- def get_context_data(self, **kwargs):
466- '''Get the context data passed to template.'''
467- context = super(AndroidLoopUpdateView,
468- self).get_context_data(**kwargs)
469- context['request'] = self.request
470- context['form_name'] = self.form_name
471- return context
472-
473- def get_success_url(self):
474- '''Return the URL when the form is valid.'''
475- return reverse('AndroidLoopDetail', args=[self.object.name])
476+ reverse_url = 'AndroidLoopDetail'
477
478=== modified file 'dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py'
479--- dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py 2012-09-04 14:29:53 +0000
480+++ dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py 2012-09-11 14:48:19 +0000
481@@ -15,31 +15,21 @@
482 # You should have received a copy of the GNU Affero General Public License
483 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
484
485-from django.forms import CharField
486-from django.forms import IntegerField
487-from django.forms.widgets import HiddenInput
488-from frontend.models.loop import Loop
489-from frontend.widgets.chain_loop_widget import ChainLoopWidget
490+from django.forms import (
491+ CharField,
492+ HiddenInput,
493+ )
494 from frontend.forms.textfield_loop_form import TextFieldLoopForm
495 from frontend.android_textfield_loop.models.android_textfield_loop \
496 import AndroidTextFieldLoop
497
498
499 class AndroidTextFieldLoopForm(TextFieldLoopForm):
500+ """
501+ Form for the AndroidTextFieldLoop model.
502+ """
503 class Meta(TextFieldLoopForm.Meta):
504 model = AndroidTextFieldLoop
505
506- next_loop = IntegerField(widget=ChainLoopWidget, required=False)
507 type = CharField(widget=HiddenInput, initial=AndroidTextFieldLoop.__name__,
508 required=False)
509-
510- def save(self, *args, **kwargs):
511- instance = super(AndroidTextFieldLoopForm, self).save(commit=False)
512-
513- if self.data["next_loop_0"]:
514- next_loop = Loop.objects.get(id=self.data["next_loop_0"])
515- instance.next_loop = next_loop
516-
517- instance.save()
518-
519- return instance
520
521=== modified file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html'
522--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html 2012-09-06 08:31:57 +0000
523+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html 2012-09-11 14:48:19 +0000
524@@ -1,12 +1,4 @@
525-{% extends "base.html" %}
526-
527-{% block content %}
528-
529-{% block create_form %}
530- <form name="{{ form_name }}" action="{% url AndroidTextFieldLoopCreate %}" method="post">
531- {% csrf_token %}
532-{% endblock create_form %}
533-{{ form.as_p }}
534- <div><input type="submit" value="Submit" /></div>
535- </form>
536-{% endblock %}
537+{% extends "loop_create.html" %}
538+{% block content_form %}
539+<form action="{% url AndroidTextFieldLoopCreate %}" method="post">{% csrf_token %}
540+{% endblock content_form %}
541
542=== modified file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html'
543--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html 2012-08-30 15:46:41 +0000
544+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html 2012-09-11 14:48:19 +0000
545@@ -1,42 +1,7 @@
546-{% extends "base.html" %}
547-
548-{% block breadcrumbs %}
549- <div><a href="{% url Index %}">Index</a></div>
550-{% endblock %}
551-
552-{% block content %}
553- <h2>Loop {{ android_loop_detail.name }}</h2>
554-
555- <h4>Details</h4>
556- <div>
557- {{ android_loop_detail.values }}
558- </div>
559- <div>
560- <button type="button" id="update_button">Update loop configuration</button>
561- </div>
562-
563- <h4>Builds</h4>
564- <div id="builds">
565- {% for build in builds %}
566- {% include "build.html" %}
567- {% endfor %}
568- </div>
569-
570- <div>
571- <button type="button" id="schedule_button">Schedule new build</button>
572- </div>
573-{% endblock %}
574-
575+{% extends "textfield_loop_detail.html" %}
576 {% block scripts %}
577- <script type="text/javascript">
578- $("#schedule_button").click(function() {
579- $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/android-text/build/{{ android_loop_detail.name }}/", function(data) {
580- $(data).prependTo($("#builds"));
581- });
582- });
583-
584- $("#update_button").click(function() {
585- window.location.href = "{% url AndroidTextFieldLoopUpdate android_loop_detail.name %}";
586- });
587- </script>
588-{% endblock %}
589+{{ block.super }}
590+$("#update_button").click(function() {
591+ window.location.href = "{% url AndroidTextFieldLoopUpdate loop_detail.name %}";
592+});
593+{% endblock scripts %}
594
595=== modified file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html'
596--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html 2012-08-31 12:25:57 +0000
597+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html 2012-09-11 14:48:19 +0000
598@@ -1,6 +1,4 @@
599-{% extends "android_textfield_loop_create.html" %}
600-
601-{% block create_form %}
602- <form name="{{ form_name }}" action="{% url AndroidTextFieldLoopUpdate android_loop_update.name %}" method="post">
603- {% csrf_token %}
604-{% endblock create_form %}
605+{% extends "loop_create.html" %}
606+{% block content_form %}
607+<form action="{% url AndroidTextFieldLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
608+{% endblock content_form %}
609
610=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py'
611--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py 2012-09-04 14:32:49 +0000
612+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py 2012-09-11 14:48:19 +0000
613@@ -40,6 +40,7 @@
614 response.templates]
615 self.assertEquals(template_names,
616 ["android_textfield_loop_create.html",
617+ "loop_create.html",
618 "base.html",
619 "login.html",
620 ])
621@@ -65,6 +66,8 @@
622 response.templates]
623 self.assertEquals(template_names,
624 ["android_textfield_loop_detail.html",
625+ "textfield_loop_detail.html",
626+ "loop_detail.html",
627 "base.html",
628 "login.html",
629 ])
630
631=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py'
632--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py 2012-09-07 10:39:47 +0000
633+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py 2012-09-11 14:48:19 +0000
634@@ -22,7 +22,11 @@
635
636
637 class AndroidTextFieldLoopModelTest(TestCase):
638-
639+ """
640+ Test class for the AndroidTextFieldLoop model. It is mostly used also for
641+ tests for the general TextFieldLoop class, since it is an abstract model
642+ class and we cannot create object for it.
643+ """
644 A_NAME = 'a-build'
645 B_NAME = 'b-build'
646 VALID_VALUES = u'a=2\nb=3'
647@@ -47,8 +51,8 @@
648 self.assertEqual({}, self.non_valid_android_loop.values_to_dict())
649
650 def test_valid_values_correct(self):
651- self.assertEqual(AndroidTextFieldLoop.valid_values(self.VALID_VALUES),
652- (True, self.VALID_LINES))
653+ self.assertEqual((True, self.VALID_LINES),
654+ AndroidTextFieldLoop.valid_values(self.VALID_VALUES))
655
656 def test_valid_values_wrong(self):
657 self.assertEqual((False, self.NON_VALID_LINES),
658
659=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py'
660--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py 2012-09-03 15:44:47 +0000
661+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py 2012-09-11 14:48:19 +0000
662@@ -37,8 +37,6 @@
663 context = AndroidTextFieldLoopCreateView.get_context_data(
664 android_textfield_create_view)
665 self.assertEqual(context['request'], "some request data")
666- self.assertEqual(context['form_name'],
667- AndroidTextFieldLoopCreateView.form_name)
668
669 def test_detail_view_get_context_data(self):
670 android_textfield_detail_view = AndroidTextFieldLoopDetailView()
671@@ -56,5 +54,3 @@
672 context = AndroidTextFieldLoopUpdateView.get_context_data(
673 android_textfield_update_view)
674 self.assertEqual(context['request'], "some request data")
675- self.assertEqual(context['form_name'],
676- AndroidTextFieldLoopUpdateView.form_name)
677
678=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py'
679--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py 2012-09-03 14:12:10 +0000
680+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py 2012-09-11 14:48:19 +0000
681@@ -1,4 +1,3 @@
682-#!/usr/bin/env python
683 # Copyright (C) 2012 Linaro
684 #
685 # This file is part of linaro-ci-dashboard.
686@@ -16,42 +15,16 @@
687 # You should have received a copy of the GNU Affero General Public License
688 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
689
690-from django.contrib.auth.decorators import login_required
691-from django.core.urlresolvers import reverse
692-from django.views.generic.edit import CreateView
693-from django.utils.decorators import method_decorator
694 from frontend.android_textfield_loop.models.android_textfield_loop \
695 import AndroidTextFieldLoop
696 from frontend.android_textfield_loop.forms.android_textfield_loop_form \
697 import AndroidTextFieldLoopForm
698-
699-
700-class AndroidTextFieldLoopCreateView(CreateView):
701+from frontend.views.loop_create_view import LoopCreateView
702+
703+
704+class AndroidTextFieldLoopCreateView(LoopCreateView):
705
706 template_name = "android_textfield_loop_create.html"
707 form_class = AndroidTextFieldLoopForm
708 model = AndroidTextFieldLoop
709- form_name = 'android_textfield_loop'
710-
711- def form_valid(self, form):
712- """This method is called when valid form data has been POSTed.
713-
714- It should return an HttpResponse."""
715- return super(AndroidTextFieldLoopCreateView, self).form_valid(form)
716-
717- @method_decorator(login_required)
718- def dispatch(self, *args, **kwargs):
719- return super(AndroidTextFieldLoopCreateView,
720- self).dispatch(*args, **kwargs)
721-
722- def get_context_data(self, **kwargs):
723- '''Get the context data passed to template.'''
724- context = super(AndroidTextFieldLoopCreateView,
725- self).get_context_data(**kwargs)
726- context['request'] = self.request
727- context['form_name'] = self.form_name
728- return context
729-
730- def get_success_url(self):
731- '''Return the URL when the form is valid.'''
732- return reverse('AndroidTextFieldLoopDetail', args=[self.object.name])
733+ reverse_url = 'AndroidTextFieldLoopDetail'
734
735=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py'
736--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py 2012-08-30 15:46:41 +0000
737+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py 2012-09-11 14:48:19 +0000
738@@ -1,4 +1,3 @@
739-#!/usr/bin/env python
740 # Copyright (C) 2012 Linaro
741 #
742 # This file is part of linaro-ci-dashboard.
743@@ -16,30 +15,13 @@
744 # You should have received a copy of the GNU Affero General Public License
745 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
746
747-from django.contrib.auth.decorators import login_required
748-from django.views.generic.detail import DetailView
749-from django.utils.decorators import method_decorator
750 from frontend.android_textfield_loop.models.android_textfield_loop \
751 import AndroidTextFieldLoop
752-
753-
754-class AndroidTextFieldLoopDetailView(DetailView):
755+from frontend.views.loop_detail_view import LoopDetailView
756+
757+
758+class AndroidTextFieldLoopDetailView(LoopDetailView):
759
760 model = AndroidTextFieldLoop
761- context_object_name = 'android_loop_detail'
762- slug_field = "name"
763- template_name = "android_textfield_loop_detail.html"
764-
765- @method_decorator(login_required)
766- def dispatch(self, *args, **kwargs):
767- return super(AndroidTextFieldLoopDetailView,
768- self).dispatch(*args, **kwargs)
769-
770- def get_context_data(self, **kwargs):
771- '''Get the context data passed to template.'''
772- context = super(AndroidTextFieldLoopDetailView,
773- self).get_context_data(**kwargs)
774- context['request'] = self.request
775- context['builds'] = self.object.loop_ptr.loopbuild_set.all()
776-
777- return context
778+ template_name = 'android_textfield_loop_detail.html'
779+ build_path = 'android-text'
780
781=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py'
782--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py 2012-09-03 14:12:10 +0000
783+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py 2012-09-11 14:48:19 +0000
784@@ -15,44 +15,16 @@
785 # You should have received a copy of the GNU Affero General Public License
786 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
787
788-from django.contrib.auth.decorators import login_required
789-from django.core.urlresolvers import reverse
790-from django.views.generic.edit import UpdateView
791-from django.utils.decorators import method_decorator
792 from frontend.android_textfield_loop.models.android_textfield_loop \
793 import AndroidTextFieldLoop
794 from frontend.android_textfield_loop.forms.android_textfield_loop_form \
795 import AndroidTextFieldLoopForm
796-
797-
798-class AndroidTextFieldLoopUpdateView(UpdateView):
799-
800- template_name = "android_textfield_loop_update.html"
801+from frontend.views.loop_update_view import LoopUpdateView
802+
803+
804+class AndroidTextFieldLoopUpdateView(LoopUpdateView):
805+
806+ template_name = 'android_textfield_loop_update.html'
807 form_class = AndroidTextFieldLoopForm
808- context_object_name = 'android_loop_update'
809 model = AndroidTextFieldLoop
810- slug_field = "name"
811- form_name = 'android_textfield_loop'
812-
813- def form_valid(self, form):
814- """This method is called when valid form data has been POSTed.
815-
816- It should return an HttpResponse."""
817- return super(AndroidTextFieldLoopUpdateView, self).form_valid(form)
818-
819- @method_decorator(login_required)
820- def dispatch(self, *args, **kwargs):
821- return super(AndroidTextFieldLoopUpdateView,
822- self).dispatch(*args, **kwargs)
823-
824- def get_context_data(self, **kwargs):
825- '''Get the context data passed to template.'''
826- context = super(AndroidTextFieldLoopUpdateView,
827- self).get_context_data(**kwargs)
828- context['request'] = self.request
829- context['form_name'] = self.form_name
830- return context
831-
832- def get_success_url(self):
833- '''Return the URL when the form is valid.'''
834- return reverse('AndroidTextFieldLoopDetail', args=[self.object.name])
835+ reverse_url = 'AndroidTextFieldLoopDetail'
836
837=== added file 'dashboard/frontend/forms/loop_form.py'
838--- dashboard/frontend/forms/loop_form.py 1970-01-01 00:00:00 +0000
839+++ dashboard/frontend/forms/loop_form.py 2012-09-11 14:48:19 +0000
840@@ -0,0 +1,48 @@
841+# Copyright (C) 2012 Linaro
842+#
843+# This file is part of linaro-ci-dashboard.
844+#
845+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
846+# it under the terms of the GNU Affero General Public License as published by
847+# the Free Software Foundation, either version 3 of the License, or
848+# (at your option) any later version.
849+#
850+# linaro-ci-dashboard is distributed in the hope that it will be useful,
851+# but WITHOUT ANY WARRANTY; without even the implied warranty of
852+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
853+# GNU Affero General Public License for more details.
854+#
855+# You should have received a copy of the GNU Affero General Public License
856+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
857+
858+from django.forms import (
859+ CharField,
860+ IntegerField,
861+ ModelForm,
862+ HiddenInput,
863+ )
864+from frontend.models.loop import Loop
865+from frontend.widgets.chain_loop_widget import ChainLoopWidget
866+
867+
868+class LoopForm(ModelForm):
869+ """
870+ Generic loop form class that all loop should inherit from.
871+ """
872+ class Meta:
873+ model = Loop
874+ exclude = ('server')
875+
876+ next_loop = IntegerField(widget=ChainLoopWidget, required=False)
877+ type = CharField(widget=HiddenInput, initial=Loop.__name__,
878+ required=False)
879+
880+ def save(self, *args, **kwargs):
881+ instance = super(LoopForm, self).save(commit=False)
882+ if self.data["next_loop_0"]:
883+ next_loop = Loop.objects.get(id=self.data["next_loop_0"])
884+ instance.next_loop = next_loop
885+
886+ instance.save()
887+
888+ return instance
889
890=== modified file 'dashboard/frontend/forms/textfield_loop_form.py'
891--- dashboard/frontend/forms/textfield_loop_form.py 2012-09-03 15:44:21 +0000
892+++ dashboard/frontend/forms/textfield_loop_form.py 2012-09-11 14:48:19 +0000
893@@ -15,24 +15,30 @@
894 # You should have received a copy of the GNU Affero General Public License
895 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
896
897-from django.forms import ModelForm
898 from django.forms import ValidationError
899 from frontend.models.textfield_loop import (
900 TextFieldLoop,
901 DEFAULT_DELIMITER,
902 )
903
904-
905-class TextFieldLoopForm(ModelForm):
906- class Meta:
907+from frontend.forms.loop_form import LoopForm
908+
909+
910+class TextFieldLoopForm(LoopForm):
911+ """
912+ General loop form for loops of the textfield kind.
913+
914+ A textfield loop should inherit from this class, that in turn inherits from
915+ the LoopForm one.
916+ """
917+ class Meta(LoopForm.Meta):
918 model = TextFieldLoop
919- exclude = ('server', 'type')
920
921 def clean(self):
922 cleaned_data = super(TextFieldLoopForm, self).clean()
923 values = cleaned_data.get('values')
924 if values:
925- valid , _ = self.instance.valid_values(values)
926+ valid, _ = self.instance.valid_values(values)
927 if not valid:
928 raise ValidationError("Wrong delimiter used: please use the "
929 "%s character as delimiter." %
930
931=== modified file 'dashboard/frontend/integration_loop/forms/integration_loop_form.py'
932--- dashboard/frontend/integration_loop/forms/integration_loop_form.py 2012-09-04 11:23:32 +0000
933+++ dashboard/frontend/integration_loop/forms/integration_loop_form.py 2012-09-11 14:48:19 +0000
934@@ -16,32 +16,20 @@
935 # You should have received a copy of the GNU Affero General Public License
936 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
937
938-from django.forms import CharField
939-from django.forms import IntegerField
940-from django.forms import ModelForm
941-from django.forms.widgets import HiddenInput
942+from django.forms import (
943+ CharField,
944+ HiddenInput,
945+ )
946+from frontend.forms.loop_form import LoopForm
947 from frontend.integration_loop.models.integration_loop import IntegrationLoop
948-from frontend.models.loop import Loop
949-from frontend.widgets.chain_loop_widget import ChainLoopWidget
950-
951-
952-class IntegrationLoopForm(ModelForm):
953-
954- next_loop = IntegerField(widget=ChainLoopWidget, required=False)
955+
956+
957+class IntegrationLoopForm(LoopForm):
958+ """
959+ Form for the IntegrationLoop model.
960+ """
961+ class Meta(LoopForm.Meta):
962+ model = IntegrationLoop
963+
964 type = CharField(widget=HiddenInput, initial=IntegrationLoop.__name__,
965 required=False)
966-
967- class Meta:
968- model = IntegrationLoop
969- exclude = ('server')
970-
971- def save(self, *args, **kwargs):
972- instance = super(IntegrationLoopForm, self).save(commit=False)
973-
974- if self.data["next_loop_0"]:
975- next_loop = Loop.objects.get(id=self.data["next_loop_0"])
976- instance.next_loop = next_loop
977-
978- instance.save()
979-
980- return instance
981
982=== modified file 'dashboard/frontend/integration_loop/templates/integration_loop_create.html'
983--- dashboard/frontend/integration_loop/templates/integration_loop_create.html 2012-08-16 15:12:10 +0000
984+++ dashboard/frontend/integration_loop/templates/integration_loop_create.html 2012-09-11 14:48:19 +0000
985@@ -1,8 +1,4 @@
986-{% extends "base.html" %}
987-
988-{% block content %}
989+{% extends "loop_create.html" %}
990+{% block content_form %}
991 <form action="{% url IntegrationLoopCreate %}" method="post">{% csrf_token %}
992-{{ form.as_p }}
993-<div><input type="submit" value="Submit" /></div>
994-</form>
995-{% endblock %}
996+{% endblock content_form %}
997
998=== modified file 'dashboard/frontend/integration_loop/templates/integration_loop_detail.html'
999--- dashboard/frontend/integration_loop/templates/integration_loop_detail.html 2012-08-31 13:34:41 +0000
1000+++ dashboard/frontend/integration_loop/templates/integration_loop_detail.html 2012-09-11 14:48:19 +0000
1001@@ -1,54 +1,7 @@
1002-{% extends "base.html" %}
1003-
1004-{% block breadcrumbs %}
1005-<div>
1006-<a href="{% url Index %}">Index</a>
1007-</div>
1008-{% endblock %}
1009-
1010-{% block content %}
1011- <h2>Loop {{ integration_loop_detail.name }}</h2>
1012-
1013- <h4>Details</h4>
1014- <div>
1015- Next loop in chain: {{ integration_loop_detail.next_loop.name }}
1016- </div>
1017- <div>
1018- Branch: {{ integration_loop_detail.branch }}
1019- </div>
1020- <div>
1021- Pre-command: {{ integration_loop_detail.precommand }}
1022- </div>
1023- <div>
1024- Test Command: {{ integration_loop_detail.command }}
1025- </div>
1026-
1027- <div>
1028- <button type="button" id="update_button">Update loop configuration</button>
1029- </div>
1030-
1031- <h4>Builds</h4>
1032- <div id="builds">
1033- {% for build in builds %}
1034- {% include "build.html" %}
1035- {% endfor %}
1036- </div>
1037-
1038- <div>
1039- <button type="button" id="schedule_button">Schedule new build</button>
1040- </div>
1041-{% endblock %}
1042-
1043+{% extends "loop_detail.html" %}
1044 {% block scripts %}
1045-<script type="text/javascript">
1046-$("#schedule_button").click(function() {
1047- $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/integration_loop/build/{{ integration_loop_detail.name }}/", function(data) {
1048- $(data).prependTo($("#builds"));
1049- });
1050-});
1051-
1052+{{ block.super }}
1053 $("#update_button").click(function() {
1054- window.location.href = "{% url IntegrationLoopUpdate integration_loop_detail.name %}";
1055+ window.location.href = "{% url IntegrationLoopUpdate loop_detail.name %}";
1056 });
1057-</script>
1058-{% endblock %}
1059+{% endblock scripts %}
1060
1061=== modified file 'dashboard/frontend/integration_loop/templates/integration_loop_update.html'
1062--- dashboard/frontend/integration_loop/templates/integration_loop_update.html 2012-08-16 15:12:10 +0000
1063+++ dashboard/frontend/integration_loop/templates/integration_loop_update.html 2012-09-11 14:48:19 +0000
1064@@ -1,8 +1,4 @@
1065-{% extends "base.html" %}
1066-
1067-{% block content %}
1068-<form action="{% url IntegrationLoopUpdate integration_loop_update.name %}" method="post">{% csrf_token %}
1069-{{ form.as_p }}
1070-<div><input type="submit" value="Submit" /></div>
1071-</form>
1072-{% endblock %}
1073+{% extends "loop_create.html" %}
1074+{% block content_form %}
1075+<form action="{% url IntegrationLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
1076+{% endblock content_form %}
1077
1078=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_create_view.py'
1079--- dashboard/frontend/integration_loop/views/integration_loop_create_view.py 2012-08-16 15:12:10 +0000
1080+++ dashboard/frontend/integration_loop/views/integration_loop_create_view.py 2012-09-11 14:48:19 +0000
1081@@ -16,39 +16,15 @@
1082 # You should have received a copy of the GNU Affero General Public License
1083 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1084
1085-from django.contrib.auth.decorators import login_required
1086-from django.core.urlresolvers import reverse
1087-from django.views.generic.edit import CreateView
1088-from django.utils.decorators import method_decorator
1089 from frontend.integration_loop.forms.integration_loop_form \
1090 import IntegrationLoopForm
1091 from frontend.integration_loop.models.integration_loop import IntegrationLoop
1092-
1093-
1094-class IntegrationLoopCreateView(CreateView):
1095+from frontend.views.loop_create_view import LoopCreateView
1096+
1097+
1098+class IntegrationLoopCreateView(LoopCreateView):
1099
1100 template_name = "integration_loop_create.html"
1101 form_class = IntegrationLoopForm
1102 model = IntegrationLoop
1103-
1104- def form_valid(self, form):
1105- """This method is called when valid form data has been POSTed.
1106-
1107- It should return an HttpResponse."""
1108-
1109- return super(IntegrationLoopCreateView, self).form_valid(form)
1110-
1111- @method_decorator(login_required)
1112- def dispatch(self, *args, **kwargs):
1113- return super(IntegrationLoopCreateView, self).dispatch(*args, **kwargs)
1114-
1115- def get_context_data(self, **kwargs):
1116- '''Get the context data passed to template.'''
1117- context = super(IntegrationLoopCreateView,
1118- self).get_context_data(**kwargs)
1119- context['request'] = self.request
1120- return context
1121-
1122- def get_success_url(self):
1123- '''Return the URL when the form is valid.'''
1124- return reverse('IntegrationLoopDetail', args=[self.object.name])
1125+ reverse_url = 'IntegrationLoopDetail'
1126
1127=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_detail_view.py'
1128--- dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-08-16 15:12:10 +0000
1129+++ dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-09-11 14:48:19 +0000
1130@@ -1,4 +1,3 @@
1131-#!/usr/bin/env python
1132 # Copyright (C) 2012 Linaro
1133 #
1134 # This file is part of linaro-ci-dashboard.
1135@@ -16,27 +15,12 @@
1136 # You should have received a copy of the GNU Affero General Public License
1137 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1138
1139-from django.contrib.auth.decorators import login_required
1140-from django.views.generic.detail import DetailView
1141-from django.utils.decorators import method_decorator
1142 from frontend.integration_loop.models.integration_loop import IntegrationLoop
1143-
1144-
1145-class IntegrationLoopDetailView(DetailView):
1146+from frontend.views.loop_detail_view import LoopDetailView
1147+
1148+
1149+class IntegrationLoopDetailView(LoopDetailView):
1150
1151 model = IntegrationLoop
1152- context_object_name = 'integration_loop_detail'
1153- slug_field = "name"
1154 template_name = "integration_loop_detail.html"
1155-
1156- @method_decorator(login_required)
1157- def dispatch(self, *args, **kwargs):
1158- return super(IntegrationLoopDetailView, self).dispatch(*args, **kwargs)
1159-
1160- def get_context_data(self, **kwargs):
1161- '''Get the context data passed to template.'''
1162- context = super(IntegrationLoopDetailView,
1163- self).get_context_data(**kwargs)
1164- context['request'] = self.request
1165- context['builds'] = self.object.loop_ptr.loopbuild_set.all()
1166- return context
1167+ build_path = 'integration_loop'
1168
1169=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_update_view.py'
1170--- dashboard/frontend/integration_loop/views/integration_loop_update_view.py 2012-08-16 15:12:10 +0000
1171+++ dashboard/frontend/integration_loop/views/integration_loop_update_view.py 2012-09-11 14:48:19 +0000
1172@@ -1,4 +1,3 @@
1173-#!/usr/bin/env python
1174 # Copyright (C) 2012 Linaro
1175 #
1176 # This file is part of linaro-ci-dashboard.
1177@@ -16,41 +15,15 @@
1178 # You should have received a copy of the GNU Affero General Public License
1179 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1180
1181-from django.contrib.auth.decorators import login_required
1182-from django.core.urlresolvers import reverse
1183-from django.views.generic.edit import UpdateView
1184-from django.utils.decorators import method_decorator
1185 from frontend.integration_loop.forms.integration_loop_form \
1186 import IntegrationLoopForm
1187 from frontend.integration_loop.models.integration_loop import IntegrationLoop
1188-
1189-
1190-class IntegrationLoopUpdateView(UpdateView):
1191-
1192- template_name = "integration_loop_update.html"
1193+from frontend.views.loop_update_view import LoopUpdateView
1194+
1195+
1196+class IntegrationLoopUpdateView(LoopUpdateView):
1197+
1198+ template_name = 'integration_loop_update.html'
1199 form_class = IntegrationLoopForm
1200- context_object_name = 'integration_loop_update'
1201 model = IntegrationLoop
1202- slug_field = "name"
1203-
1204- def form_valid(self, form):
1205- """This method is called when valid form data has been POSTed.
1206-
1207- It should return an HttpResponse."""
1208-
1209- return super(IntegrationLoopUpdateView, self).form_valid(form)
1210-
1211- @method_decorator(login_required)
1212- def dispatch(self, *args, **kwargs):
1213- return super(IntegrationLoopUpdateView, self).dispatch(*args, **kwargs)
1214-
1215- def get_context_data(self, **kwargs):
1216- '''Get the context data passed to template.'''
1217- context = super(IntegrationLoopUpdateView,
1218- self).get_context_data(**kwargs)
1219- context['request'] = self.request
1220- return context
1221-
1222- def get_success_url(self):
1223- '''Return the URL when the form is valid.'''
1224- return reverse('IntegrationLoopDetail', args=[self.object.name])
1225+ reverse_url = 'IntegrationLoopDetail'
1226
1227=== modified file 'dashboard/frontend/kernel_build/forms/kernel_loop_form.py'
1228--- dashboard/frontend/kernel_build/forms/kernel_loop_form.py 2012-09-04 11:23:32 +0000
1229+++ dashboard/frontend/kernel_build/forms/kernel_loop_form.py 2012-09-11 14:48:19 +0000
1230@@ -16,31 +16,20 @@
1231 # You should have received a copy of the GNU Affero General Public License
1232 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1233
1234-from django.forms import CharField
1235-from django.forms import IntegerField
1236-from django.forms import ModelForm
1237-from django.forms.widgets import HiddenInput
1238+from django.forms import (
1239+ CharField,
1240+ HiddenInput,
1241+)
1242+from frontend.forms.loop_form import LoopForm
1243 from frontend.kernel_build.models.kernel_loop import KernelLoop
1244-from frontend.widgets.chain_loop_widget import ChainLoopWidget
1245-
1246-
1247-class KernelLoopForm(ModelForm):
1248-
1249- next_loop = IntegerField(widget=ChainLoopWidget, required=False)
1250+
1251+
1252+class KernelLoopForm(LoopForm):
1253+ """
1254+ Form class for the KernelLoop model.
1255+ """
1256+ class Meta(LoopForm.Meta):
1257+ model = KernelLoop
1258+
1259 type = CharField(widget=HiddenInput, initial=KernelLoop.__name__,
1260 required=False)
1261-
1262- class Meta:
1263- model = KernelLoop
1264- exclude = ('server')
1265-
1266- def save(self, *args, **kwargs):
1267- instance = super(KernelLoopForm, self).save(commit=False)
1268-
1269- if self.data["next_loop_0"]:
1270- next_loop = Loop.objects.get(id=self.data["next_loop_0"])
1271- instance.next_loop = next_loop
1272-
1273- instance.save()
1274-
1275- return instance
1276
1277=== modified file 'dashboard/frontend/kernel_build/templates/kernel_loop_create.html'
1278--- dashboard/frontend/kernel_build/templates/kernel_loop_create.html 2012-08-17 11:58:18 +0000
1279+++ dashboard/frontend/kernel_build/templates/kernel_loop_create.html 2012-09-11 14:48:19 +0000
1280@@ -1,8 +1,4 @@
1281-{% extends "base.html" %}
1282-
1283-{% block content %}
1284+{% extends "loop_create.html" %}
1285+{% block content_form %}
1286 <form action="{% url KernelLoopCreate %}" method="post">{% csrf_token %}
1287-{{ form.as_p }}
1288-<div><input type="submit" value="Submit" /></div>
1289-</form>
1290-{% endblock %}
1291+{% endblock content_form %}
1292
1293=== modified file 'dashboard/frontend/kernel_build/templates/kernel_loop_detail.html'
1294--- dashboard/frontend/kernel_build/templates/kernel_loop_detail.html 2012-08-27 15:14:06 +0000
1295+++ dashboard/frontend/kernel_build/templates/kernel_loop_detail.html 2012-09-11 14:48:19 +0000
1296@@ -1,49 +1,7 @@
1297-{% extends "base.html" %}
1298-
1299-{% block breadcrumbs %}
1300-<div>
1301-<a href="{% url Index %}">Index</a>
1302-</div>
1303-{% endblock %}
1304-
1305-{% block content %}
1306- <h2>Loop {{ kernel_loop_detail.name }}</h2>
1307-
1308- <h4>Details</h4>
1309- <div>
1310- <table>
1311- {% for k, v in kernel_loop_detail.json.items %}
1312- <tr><td>{{ k }}</td><td>{{ v }}</td></tr>
1313- {% endfor %}
1314- </table>
1315- </div>
1316-
1317- <div>
1318- <button type="button" id="update_button">Update loop configuration</button>
1319- </div>
1320-
1321- <h4>Builds</h4>
1322- <div id="builds">
1323- {% for build in builds %}
1324- {% include "build.html" %}
1325- {% endfor %}
1326- </div>
1327-
1328- <div>
1329- <button type="button" id="schedule_button">Schedule new build</button>
1330- </div>
1331-{% endblock %}
1332-
1333+{% extends "loop_detail.html" %}
1334 {% block scripts %}
1335-<script type="text/javascript">
1336-$("#schedule_button").click(function() {
1337- $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/kernel/build/{{ kernel_loop_detail.name }}/", function(data) {
1338- $(data).prependTo($("#builds"));
1339- });
1340-});
1341-
1342+{{ block.super }}
1343 $("#update_button").click(function() {
1344- window.location.href = "{% url KernelLoopUpdate kernel_loop_detail.name %}";
1345+ window.location.href = "{% url KernelLoopUpdate loop_detail.name %}";
1346 });
1347-</script>
1348-{% endblock %}
1349+{% endblock scripts %}
1350
1351=== modified file 'dashboard/frontend/kernel_build/templates/kernel_loop_update.html'
1352--- dashboard/frontend/kernel_build/templates/kernel_loop_update.html 2012-08-20 20:12:55 +0000
1353+++ dashboard/frontend/kernel_build/templates/kernel_loop_update.html 2012-09-11 14:48:19 +0000
1354@@ -1,8 +1,4 @@
1355-{% extends "base.html" %}
1356-
1357-{% block content %}
1358-<form action="{% url KernelLoopUpdate kernel_loop_update.name %}" method="post">{% csrf_token %}
1359-{{ form.as_p }}
1360-<div><input type="submit" value="Submit" /></div>
1361-</form>
1362-{% endblock %}
1363+{% extends "loop_create.html" %}
1364+{% block content_form %}
1365+<form action="{% url KernelLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
1366+{% endblock content_form %}
1367
1368=== modified file 'dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py'
1369--- dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py 2012-09-04 14:32:49 +0000
1370+++ dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py 2012-09-11 14:48:19 +0000
1371@@ -46,6 +46,7 @@
1372 template_names = [template.name for template in
1373 response.templates]
1374 self.assertEquals(template_names, ["kernel_loop_create.html",
1375+ "loop_create.html",
1376 "base.html",
1377 "login.html",
1378 ])
1379@@ -70,6 +71,7 @@
1380 template_names = [template.name for template in
1381 response.templates]
1382 self.assertEquals(template_names, ["kernel_loop_detail.html",
1383+ "loop_detail.html",
1384 "base.html",
1385 "login.html",
1386 ])
1387
1388=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_create_view.py'
1389--- dashboard/frontend/kernel_build/views/kernel_loop_create_view.py 2012-08-20 20:12:55 +0000
1390+++ dashboard/frontend/kernel_build/views/kernel_loop_create_view.py 2012-09-11 14:48:19 +0000
1391@@ -1,4 +1,3 @@
1392-#!/usr/bin/env python
1393 # Copyright (C) 2012 Linaro
1394 #
1395 # This file is part of linaro-ci-dashboard.
1396@@ -16,39 +15,15 @@
1397 # You should have received a copy of the GNU Affero General Public License
1398 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1399
1400-from django.contrib.auth.decorators import login_required
1401-from django.core.urlresolvers import reverse
1402-from django.views.generic.edit import CreateView
1403-from django.utils.decorators import method_decorator
1404 from frontend.kernel_build.forms.kernel_loop_form \
1405 import KernelLoopForm
1406 from frontend.kernel_build.models.kernel_loop import KernelLoop
1407-
1408-
1409-class KernelLoopCreateView(CreateView):
1410+from frontend.views.loop_create_view import LoopCreateView
1411+
1412+
1413+class KernelLoopCreateView(LoopCreateView):
1414
1415 template_name = "kernel_loop_create.html"
1416 form_class = KernelLoopForm
1417 model = KernelLoop
1418-
1419- def form_valid(self, form):
1420- """This method is called when valid form data has been POSTed.
1421-
1422- It should return an HttpResponse."""
1423-
1424- return super(KernelLoopCreateView, self).form_valid(form)
1425-
1426- @method_decorator(login_required)
1427- def dispatch(self, *args, **kwargs):
1428- return super(KernelLoopCreateView, self).dispatch(*args, **kwargs)
1429-
1430- def get_context_data(self, **kwargs):
1431- '''Get the context data passed to template.'''
1432- context = super(KernelLoopCreateView,
1433- self).get_context_data(**kwargs)
1434- context['request'] = self.request
1435- return context
1436-
1437- def get_success_url(self):
1438- '''Return the URL when the form is valid.'''
1439- return reverse('KernelLoopDetail', args=[self.object.name])
1440+ reverse_url = 'KernelLoopDetail'
1441
1442=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py'
1443--- dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py 2012-08-20 20:12:55 +0000
1444+++ dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py 2012-09-11 14:48:19 +0000
1445@@ -1,4 +1,3 @@
1446-#!/usr/bin/env python
1447 # Copyright (C) 2012 Linaro
1448 #
1449 # This file is part of linaro-ci-dashboard.
1450@@ -16,27 +15,12 @@
1451 # You should have received a copy of the GNU Affero General Public License
1452 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1453
1454-from django.contrib.auth.decorators import login_required
1455-from django.views.generic.detail import DetailView
1456-from django.utils.decorators import method_decorator
1457 from frontend.kernel_build.models.kernel_loop import KernelLoop
1458-
1459-
1460-class KernelLoopDetailView(DetailView):
1461+from frontend.views.loop_detail_view import LoopDetailView
1462+
1463+
1464+class KernelLoopDetailView(LoopDetailView):
1465
1466 model = KernelLoop
1467- context_object_name = 'kernel_loop_detail'
1468- slug_field = "name"
1469- template_name = "kernel_loop_detail.html"
1470-
1471- @method_decorator(login_required)
1472- def dispatch(self, *args, **kwargs):
1473- return super(KernelLoopDetailView, self).dispatch(*args, **kwargs)
1474-
1475- def get_context_data(self, **kwargs):
1476- '''Get the context data passed to template.'''
1477- context = super(KernelLoopDetailView,
1478- self).get_context_data(**kwargs)
1479- context['request'] = self.request
1480- context['builds'] = self.object.loop_ptr.loopbuild_set.all()
1481- return context
1482+ template_name = 'kernel_loop_detail.html'
1483+ build_path = 'kernel'
1484
1485=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_update_view.py'
1486--- dashboard/frontend/kernel_build/views/kernel_loop_update_view.py 2012-08-20 20:12:55 +0000
1487+++ dashboard/frontend/kernel_build/views/kernel_loop_update_view.py 2012-09-11 14:48:19 +0000
1488@@ -16,41 +16,15 @@
1489 # You should have received a copy of the GNU Affero General Public License
1490 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1491
1492-from django.contrib.auth.decorators import login_required
1493-from django.core.urlresolvers import reverse
1494-from django.views.generic.edit import UpdateView
1495-from django.utils.decorators import method_decorator
1496 from frontend.kernel_build.forms.kernel_loop_form \
1497 import KernelLoopForm
1498 from frontend.kernel_build.models.kernel_loop import KernelLoop
1499-
1500-
1501-class KernelLoopUpdateView(UpdateView):
1502-
1503- template_name = "kernel_loop_update.html"
1504+from frontend.views.loop_update_view import LoopUpdateView
1505+
1506+
1507+class KernelLoopUpdateView(LoopUpdateView):
1508+
1509+ template_name = 'kernel_loop_update.html'
1510 form_class = KernelLoopForm
1511- context_object_name = 'kernel_loop_update'
1512 model = KernelLoop
1513- slug_field = "name"
1514-
1515- def form_valid(self, form):
1516- """This method is called when valid form data has been POSTed.
1517-
1518- It should return an HttpResponse."""
1519-
1520- return super(KernelLoopUpdateView, self).form_valid(form)
1521-
1522- @method_decorator(login_required)
1523- def dispatch(self, *args, **kwargs):
1524- return super(KernelLoopUpdateView, self).dispatch(*args, **kwargs)
1525-
1526- def get_context_data(self, **kwargs):
1527- '''Get the context data passed to template.'''
1528- context = super(KernelLoopUpdateView,
1529- self).get_context_data(**kwargs)
1530- context['request'] = self.request
1531- return context
1532-
1533- def get_success_url(self):
1534- '''Return the URL when the form is valid.'''
1535- return reverse('KernelLoopDetail', args=[self.object.name])
1536+ reverse_url = 'KernelLoopDetail'
1537
1538=== modified file 'dashboard/frontend/models/loop.py'
1539--- dashboard/frontend/models/loop.py 2012-09-10 15:23:53 +0000
1540+++ dashboard/frontend/models/loop.py 2012-09-11 14:48:19 +0000
1541@@ -31,7 +31,7 @@
1542 default=None,
1543 null=True,
1544 on_delete=models.SET_NULL)
1545- type = models.CharField(max_length=50, editable=False)
1546+ type = models.CharField(max_length=50, editable=False, blank=True)
1547 is_restricted = models.BooleanField(default=False,
1548 verbose_name='Build is restricted')
1549 is_official = models.BooleanField(default=False,
1550@@ -41,11 +41,6 @@
1551 related_name='previous_loop',
1552 verbose_name='Next loop in chain')
1553
1554- # List of fields to be excluded by default from the base64 encoded config.
1555- # If a subclass needs different fields, it is necessary to override this
1556- # variable.
1557- EXCLUDE_LIST = ['id', 'server', 'loop_ptr', 'next_loop', 'name', 'type']
1558-
1559 def __init__(self, *args, **kwargs):
1560 self.log = Logger.getClassLogger(self)
1561 super(Loop, self).__init__(*args, **kwargs)
1562@@ -125,16 +120,32 @@
1563 return []
1564
1565 def __repr__(self):
1566- return "%s(id=%s, ...)" % (self.__class__.__name__, self.id)
1567+ return "%s(id=%s)" % (self.__class__.__name__, self.id)
1568
1569- def base64_config(self):
1570+ def base64_config(self,
1571+ include=None,
1572+ exclude=None,
1573+ capitalize=False,
1574+ upper=False):
1575 """ Return loop configuration as base64 encoded string.
1576+
1577+ :param include: the list of fields, as string, to get.
1578+ :type include list
1579+ :param exclude: the list of fields, as string, to exclude.
1580+ :type exclude list
1581+ :param capitalize: whether the field name should be capitalized (the
1582+ first letter should be a capital one).
1583+ :type capitalize bool
1584+ :param upper: whether the field name should be all capital letter.
1585+ :type upper bool
1586+ :return A base64 encoded string composed of 'key=value'
1587 """
1588 text_config = u''
1589- for field in self._meta.fields:
1590- if not field.name in self.EXCLUDE_LIST:
1591- text_config += u'%s=%s\n' % (field.name.upper(),
1592- field.value_to_string(self))
1593+ for field in self._get_wanted_fields(include=include,
1594+ exclude=exclude,
1595+ capitalize=capitalize,
1596+ upper=upper):
1597+ text_config += u'%s=%s\n' % (field[0], str(field[1]))
1598 return base64.b64encode(text_config)
1599
1600 def get_child_object(self):
1601@@ -150,6 +161,87 @@
1602 result.extend(Loop.objects.filter(type=loop_type))
1603 return result
1604
1605+ def _get_wanted_fields(self,
1606+ include=None,
1607+ exclude=None,
1608+ verbose_name=False,
1609+ capitalize=False,
1610+ upper=False):
1611+ """
1612+ Gets the wanted fields, as a list of tuples with
1613+ (field.name, field.value). By default retrieves all the fields
1614+ associated with the loop instance.
1615+
1616+ There is some control over the results: it is possible to retrieve only
1617+ a subset of the fields, to retrieve the verbose name associated with
1618+ the fields, and either to capitalize or make all capital letters
1619+ the name.
1620+
1621+ The parameters used to retrieve a subset of fields are 'include' and
1622+ 'exclude'. If both are used, 'exclude' has precedence, meaning that if
1623+ a field is specified both in 'exclude' and in 'include', that result
1624+ will not include that field.
1625+
1626+ :param include: the list of fields, as string, to get.
1627+ :type include list
1628+ :param exclude: the list of fields, as string, to exclude.
1629+ :type exclude list
1630+ :param verbose_name: whether to retrieve the verbose name associated
1631+ with the field. Default False.
1632+ :type verbose_name bool
1633+ :param capitalize: whether the field name should be capitalized (the
1634+ first letter should be a capital one).
1635+ :type capitalize bool
1636+ :param upper: whether the field name should be all capital letter.
1637+ :type upper bool
1638+ :return a list of tuples, in which each tuple consists of
1639+ (field.name, field.value).
1640+ """
1641+ wanted_fields = []
1642+ for field in self._meta.fields:
1643+ # First check if field is excluded, in case both 'include'
1644+ # and 'exclude' are passed. Exclude wins.
1645+ if exclude and field.name in exclude:
1646+ continue
1647+ if include and not field.name in include:
1648+ continue
1649+
1650+ # Consider OneToOneField, since we do not have the value stored,
1651+ # we need to retrieve it.
1652+ value = None
1653+ if isinstance(field, models.related.OneToOneField):
1654+ pk = field.value_from_object(self)
1655+ if pk:
1656+ value = Loop.objects.get(pk=pk).name
1657+ else:
1658+ value = field._get_val_from_obj(self)
1659+
1660+ if verbose_name:
1661+ name = field.verbose_name
1662+ else:
1663+ name = field.name
1664+ if capitalize:
1665+ name = name.capitalize()
1666+ if upper:
1667+ name = name.upper()
1668+
1669+ wanted_fields.append((name, value))
1670+ return wanted_fields
1671+
1672+ def get_details(self):
1673+ """
1674+ Retrieves the necessary fields for the detail view.
1675+ This is mostly and probably only needed in the detail HTML template.
1676+
1677+ The fields retrieved here are not all the fields in the model.
1678+
1679+ :return A list of tuples.
1680+ """
1681+ excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr']
1682+ return self._get_wanted_fields(exclude=excluded_fields,
1683+ verbose_name=True,
1684+ capitalize=True)
1685+
1686 def continue_down_the_chain(self, configuration=None):
1687 """Invokes the build of the next loop in chain.
1688
1689
1690=== modified file 'dashboard/frontend/models/textfield_loop.py'
1691--- dashboard/frontend/models/textfield_loop.py 2012-09-10 12:25:33 +0000
1692+++ dashboard/frontend/models/textfield_loop.py 2012-09-11 14:48:19 +0000
1693@@ -83,11 +83,22 @@
1694
1695 if valid:
1696 for line in lines:
1697- key, value = line.split(DEFAULT_DELIMITER)
1698- text_to_dict[key.lower()] = value
1699+ key, value = self.split_line(line)
1700+ text_to_dict[key] = value
1701
1702 return text_to_dict
1703
1704+ def split_line(self, line):
1705+ """
1706+ Splits the line on the default delimiter defined in
1707+ 'DEFAULT_DELIMITER'.
1708+
1709+ :param line: the line to split.
1710+ :type line str
1711+ return A tuple with (name, value).
1712+ """
1713+ return line.split(DEFAULT_DELIMITER)
1714+
1715 @staticmethod
1716 def valid_values(values):
1717 """
1718@@ -97,10 +108,11 @@
1719 :param values: the string with all the key<>value pairs, separated with
1720 a newline character.
1721 :type values unicode
1722- :return a boolean for the validity, and the list of lines.
1723+ :return A tuple with the first element a boolean for the validity, the
1724+ second element the list of all the lines.
1725 """
1726 valid = True
1727- lines = values.splitlines()
1728+ lines = TextFieldLoop._get_all_lines(values)
1729 for line in lines:
1730 if DEFAULT_DELIMITER in line:
1731 continue
1732@@ -109,12 +121,21 @@
1733 break
1734 return valid, lines
1735
1736+ @staticmethod
1737+ def _get_all_lines(values):
1738+ """
1739+ Returns all the lines in the textfield as a single list.
1740+
1741+ :return The list of lines in the textfield.
1742+ """
1743+ return values.splitlines()
1744+
1745 def values_to_xml(self):
1746 """
1747 Converts the necessary values into an XML tree.
1748
1749- :return The XML tree as a string, or an empty string if the inserted
1750- values are not valid.
1751+ :return The XML tree as a string, or an empty string if there are no
1752+ valid values.
1753 """
1754 xml_string = ""
1755 valid, lines = self.valid_values(self.values)
1756@@ -125,4 +146,25 @@
1757 if other_values:
1758 values.update(other_values)
1759 xml_string = DictToXml(values).dict_to_tree()
1760+
1761 return xml_string
1762+
1763+ def get_details(self):
1764+ # We need to override this here, since we want each value inserted in
1765+ # the textfield to appear on a single line. We skip the field here
1766+ # and define a separate functions to retrieve the lines from the
1767+ # HTML template.
1768+ excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr',
1769+ 'values']
1770+ return self._get_wanted_fields(exclude=excluded_fields,
1771+ verbose_name=True,
1772+ capitalize=True)
1773+
1774+ def get_values(self):
1775+ """
1776+ This method is mostly used in the detail view for the textfield loops.
1777+ It returns the list of single string entered in the textfield.
1778+
1779+ :return A list of strings.
1780+ """
1781+ return self._get_all_lines(self.values)
1782
1783=== added file 'dashboard/frontend/templates/__init__.py'
1784=== modified file 'dashboard/frontend/templates/base.html'
1785--- dashboard/frontend/templates/base.html 2012-09-03 12:04:52 +0000
1786+++ dashboard/frontend/templates/base.html 2012-09-11 14:48:19 +0000
1787@@ -2,35 +2,27 @@
1788 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
1789 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
1790 <head>
1791-<title>
1792-{% block title %}
1793-{% endblock %}
1794-</title>
1795+<title>{% block title %} {% endblock title %}</title>
1796 <script type="text/javascript" src="{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/js/jquery-1.7.2.js"></script>
1797 <script type="text/javascript" src="{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/js/jquery-ui-1.8.23.custom.min.js"></script>
1798 <link rel="stylesheet" type="text/css" href="{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/css/ui-lightness/jquery-ui-1.8.23.custom.css" />
1799 {% block extrahead %}
1800- {{ form.media }}
1801-{% endblock %}
1802+{{ form.media }}
1803+{% endblock extrahead %}
1804 <meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
1805 {% block style %}
1806-{% endblock %}
1807-
1808+{% endblock style %}
1809 {% block headcontent %}
1810-{% endblock %}
1811+{% endblock headcontent %}
1812 </head>
1813 <body>
1814-{% block login %}
1815-{% include "login.html" %}
1816-{% endblock %}
1817-
1818-{% block breadcrumbs %}
1819-{% endblock %}
1820-
1821-{% block content %}
1822-{% endblock %}
1823-
1824-{% block scripts %}
1825-{% endblock %}
1826+{% block login %}{% include "login.html" %}{% endblock login %}
1827+{% block breadcrumbs %} {% endblock breadcrumbs %}
1828+<div class="content">
1829+{% block content %}{% endblock content %}
1830+</div>
1831+<script type="text/javascript">
1832+{% block scripts %} {% endblock scripts %}
1833+</script>
1834 </body>
1835 </html>
1836
1837=== modified file 'dashboard/frontend/templates/index.html'
1838--- dashboard/frontend/templates/index.html 2012-09-03 14:12:10 +0000
1839+++ dashboard/frontend/templates/index.html 2012-09-11 14:48:19 +0000
1840@@ -66,7 +66,6 @@
1841 {% endblock %}
1842
1843 {% block scripts %}
1844-<script type="text/javascript">
1845 $("#create_button").click(function() {
1846 window.location.href = "{% url IntegrationLoopCreate %}";
1847 });
1848@@ -79,5 +78,4 @@
1849 $("#create_android_textfield").click(function() {
1850 window.location.href = "{% url AndroidTextFieldLoopCreate %}";
1851 });
1852-</script>
1853 {% endblock %}
1854
1855=== added file 'dashboard/frontend/templates/loop_create.html'
1856--- dashboard/frontend/templates/loop_create.html 1970-01-01 00:00:00 +0000
1857+++ dashboard/frontend/templates/loop_create.html 2012-09-11 14:48:19 +0000
1858@@ -0,0 +1,34 @@
1859+{% extends "base.html" %}
1860+{% block breadcrumbs %}
1861+ <div><a href="{% url Index %}">Index</a></div>
1862+{% endblock %}
1863+{% block content %}
1864+ {% block content_form %}
1865+ {% comment %}
1866+ This is where the Django default csrf_token stuff has to go. Each sub-app
1867+ extending the base classes and templates has to override only this part
1868+ in the template.
1869+ {% endcomment %}
1870+ {% endblock content_form %}
1871+ {% if form.non_field_errors %}
1872+ <div class="form_error">
1873+ {% for err in form.non_field_errors %}
1874+ <span class="error_message">{{ err }}</span>
1875+ {% endfor %}
1876+ </div>
1877+ {% endif %}
1878+ {% for field in form %}
1879+ {% if field.is_hidden %}
1880+ <div style="display:none;">{{ field }}</div>
1881+ {% else %}
1882+ <div class="form_field">
1883+ {{ field.label_tag }}&#58;&nbsp;{{ field }}
1884+ {% for err in field.errors %}
1885+ <span class="error_message">{{ err }}</span>
1886+ {% endfor %}
1887+ </div>
1888+ {% endif %}
1889+ {% endfor %}
1890+<div><input type="submit" value="Submit" /></div>
1891+</form>
1892+{% endblock content %}
1893
1894=== added file 'dashboard/frontend/templates/loop_detail.html'
1895--- dashboard/frontend/templates/loop_detail.html 1970-01-01 00:00:00 +0000
1896+++ dashboard/frontend/templates/loop_detail.html 2012-09-11 14:48:19 +0000
1897@@ -0,0 +1,32 @@
1898+{% extends "base.html" %}
1899+{% block breadcrumbs %}
1900+ <div><a href="{% url Index %}">Index</a></div>
1901+{% endblock %}
1902+{% block content %}
1903+ <div class="header"><h2>Loop {{ loop_detail.name }}</h2></div>
1904+ <div class="header"><h4>Details</h4></div>
1905+ <div>
1906+ {% for name, value in loop_detail.get_details %}
1907+ <div>{{ name }}&#58;&nbsp;{{ value }}</div>
1908+ {% endfor %}
1909+ </div>
1910+ <div>
1911+ <button type="button" id="update_button">Update loop configuration</button>
1912+ </div>
1913+ <div class="header"><h4>Builds</h4></div>
1914+ <div id="builds">
1915+ {% for build in builds %}
1916+ {% include "build.html" %}
1917+ {% endfor %}
1918+ </div>
1919+ <div>
1920+ <button type="button" id="schedule_button">Schedule new build</button>
1921+ </div>
1922+{% endblock content %}
1923+{% block scripts %}
1924+$("#schedule_button").click(function() {
1925+ $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/{{ build_path }}/build/{{ loop_detail.name }}/", function(data) {
1926+ $(data).prependTo($("#builds"));
1927+ });
1928+});
1929+{% endblock scripts %}
1930\ No newline at end of file
1931
1932=== added file 'dashboard/frontend/templates/textfield_loop_detail.html'
1933--- dashboard/frontend/templates/textfield_loop_detail.html 1970-01-01 00:00:00 +0000
1934+++ dashboard/frontend/templates/textfield_loop_detail.html 2012-09-11 14:48:19 +0000
1935@@ -0,0 +1,28 @@
1936+{% extends "loop_detail.html" %}
1937+{% block content %}
1938+ <div class="header"><h2>Loop {{ loop_detail.name }}</h2></div>
1939+ <div class="header"><h4>Details</h4></div>
1940+ <div>
1941+ {% for name, value in loop_detail.get_details %}
1942+ <div>{{ name }}&#58;&nbsp;{{ value }}</div>
1943+ {% endfor %}
1944+ </div>
1945+ <div class="header"><h4>Values</h4></div>
1946+ <div class="textfield">
1947+ {% for value in loop_detail.get_values %}
1948+ <div>{{ value }}</div>
1949+ {% endfor %}
1950+ </div>
1951+ <div>
1952+ <button type="button" id="update_button">Update loop configuration</button>
1953+ </div>
1954+ <div class="header"><h4>Builds</h4></div>
1955+ <div id="builds">
1956+ {% for build in builds %}
1957+ {% include "build.html" %}
1958+ {% endfor %}
1959+ </div>
1960+ <div>
1961+ <button type="button" id="schedule_button">Schedule new build</button>
1962+ </div>
1963+{% endblock content %}
1964
1965=== modified file 'dashboard/frontend/templatetags/dashboard_extras.py'
1966--- dashboard/frontend/templatetags/dashboard_extras.py 2012-08-08 09:21:46 +0000
1967+++ dashboard/frontend/templatetags/dashboard_extras.py 2012-09-11 14:48:19 +0000
1968@@ -1,10 +1,7 @@
1969 from django import template
1970-from django.template.defaultfilters import stringfilter
1971-
1972
1973 register = template.Library()
1974
1975 @register.filter
1976 def verbose_name(value, field):
1977 return value._meta.get_field(field).verbose_name
1978-
1979
1980=== modified file 'dashboard/frontend/tests/test_clientresponse.py'
1981--- dashboard/frontend/tests/test_clientresponse.py 2012-09-04 11:23:32 +0000
1982+++ dashboard/frontend/tests/test_clientresponse.py 2012-09-11 14:48:19 +0000
1983@@ -82,6 +82,7 @@
1984 template_names = [template.name for template in
1985 response.templates]
1986 self.assertEquals(template_names, ["integration_loop_create.html",
1987+ "loop_create.html",
1988 "base.html",
1989 "login.html",
1990 ])
1991@@ -108,6 +109,7 @@
1992 template_names = [template.name for template in
1993 response.templates]
1994 self.assertEquals(template_names, ["integration_loop_detail.html",
1995+ "loop_detail.html",
1996 "base.html",
1997 "login.html",
1998 ])
1999
2000=== modified file 'dashboard/frontend/tests/test_models.py'
2001--- dashboard/frontend/tests/test_models.py 2012-09-10 12:25:33 +0000
2002+++ dashboard/frontend/tests/test_models.py 2012-09-11 14:48:19 +0000
2003@@ -93,6 +93,66 @@
2004 self.assertEqual(expected_out,
2005 self.loop.dict_for_xml(exclude=exclude_list))
2006
2007+ def test_wanted_fields_one_field_included(self):
2008+ include = ['type']
2009+ expected_out = [('type', Loop.__class__.__name__)]
2010+ self.assertEqual(expected_out,
2011+ self.loop._get_wanted_fields(include=include))
2012+
2013+ def test_wanted_fields_two_fields_included(self):
2014+ include = ['type', 'is_restricted']
2015+ expected_out = [
2016+ ('type', Loop.__class__.__name__),
2017+ ('is_restricted', False)
2018+ ]
2019+ self.assertEqual(expected_out,
2020+ self.loop._get_wanted_fields(include=include))
2021+
2022+ def test_wanted_fields_one_field_excluded(self):
2023+ exclude = ['type']
2024+ expected_out = [
2025+ ('id', self.loop.id),
2026+ ('name', 'a-loop'),
2027+ ('server', None),
2028+ ('is_restricted', False),
2029+ ('is_official', False),
2030+ ('next_loop', None)
2031+ ]
2032+ self.assertEqual(expected_out,
2033+ self.loop._get_wanted_fields(exclude=exclude))
2034+
2035+ def test_wanted_fields_two_fields_excluded(self):
2036+ exclude = ['type', 'id']
2037+ expected_out = [
2038+ ('name', 'a-loop'),
2039+ ('server', None),
2040+ ('is_restricted', False),
2041+ ('is_official', False),
2042+ ('next_loop', None)
2043+ ]
2044+ self.assertEqual(expected_out,
2045+ self.loop._get_wanted_fields(exclude=exclude))
2046+
2047+ def test_wanted_fields_include_and_exclude(self):
2048+ exclude = ['type']
2049+ include = ['type', 'id']
2050+ expected_out = [
2051+ ('id', self.loop.id)
2052+ ]
2053+ self.assertEqual(expected_out,
2054+ self.loop._get_wanted_fields(include=include,
2055+ exclude=exclude))
2056+
2057+ def test_convert_to_base64(self):
2058+ from base64 import b64encode
2059+ exclude = ['type', 'id']
2060+ fields = self.loop._get_wanted_fields(exclude=exclude, upper=True)
2061+ encoded_string = u''
2062+ for field in fields:
2063+ encoded_string += u'%s=%s\n' % (field[0], str(field[1]))
2064+ self.assertEqual(b64encode(encoded_string),
2065+ self.loop.base64_config(exclude=exclude, upper=True))
2066+
2067 def test_continue_down_the_chain_no_next_loop(self):
2068 self.loop.continue_down_the_chain()
2069 self.assertEqual(0, len(self.integration_loop.loopbuild_set.all()))
2070
2071=== modified file 'dashboard/frontend/urls.py'
2072--- dashboard/frontend/urls.py 2012-08-31 15:21:11 +0000
2073+++ dashboard/frontend/urls.py 2012-09-11 14:48:19 +0000
2074@@ -31,5 +31,4 @@
2075 LoopBuildView.as_view(), name='LoopBuild'),
2076 url(r'^loop/get_chainable/$',
2077 LoopGetChainableView.as_view(), name='LoopGetChainable'),
2078-
2079 )
2080
2081=== modified file 'dashboard/frontend/views/loop_build_view.py'
2082--- dashboard/frontend/views/loop_build_view.py 2012-08-27 16:00:38 +0000
2083+++ dashboard/frontend/views/loop_build_view.py 2012-09-11 14:48:19 +0000
2084@@ -26,8 +26,10 @@
2085
2086 class LoopBuildView(DetailView):
2087
2088+ # This field should *never* be overridden.
2089+ slug_field = "name"
2090+ # The following field has to be overridden in each sub-class.
2091 model = Loop
2092- slug_field = "name"
2093
2094 @method_decorator(login_required)
2095 def dispatch(self, *args, **kwargs):
2096
2097=== added file 'dashboard/frontend/views/loop_create_view.py'
2098--- dashboard/frontend/views/loop_create_view.py 1970-01-01 00:00:00 +0000
2099+++ dashboard/frontend/views/loop_create_view.py 2012-09-11 14:48:19 +0000
2100@@ -0,0 +1,52 @@
2101+# Copyright (C) 2012 Linaro
2102+#
2103+# This file is part of linaro-ci-dashboard.
2104+#
2105+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
2106+# it under the terms of the GNU Affero General Public License as published by
2107+# the Free Software Foundation, either version 3 of the License, or
2108+# (at your option) any later version.
2109+#
2110+# linaro-ci-dashboard is distributed in the hope that it will be useful,
2111+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2112+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2113+# GNU Affero General Public License for more details.
2114+#
2115+# You should have received a copy of the GNU Affero General Public License
2116+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
2117+
2118+from django.contrib.auth.decorators import login_required
2119+from django.core.urlresolvers import reverse
2120+from django.views.generic.edit import CreateView
2121+from django.utils.decorators import method_decorator
2122+from frontend.models.loop import Loop
2123+from frontend.forms.loop_form import LoopForm
2124+
2125+
2126+class LoopCreateView(CreateView):
2127+
2128+ # All these fields have to be overridden in each sub-class.
2129+ template_name = 'loop_create.html'
2130+ form_class = LoopForm
2131+ model = Loop
2132+ reverse_url = 'LoopDetail'
2133+
2134+ def form_valid(self, form):
2135+ """This method is called when valid form data has been POSTed.
2136+
2137+ It should return an HttpResponse."""
2138+ return super(LoopCreateView, self).form_valid(form)
2139+
2140+ @method_decorator(login_required)
2141+ def dispatch(self, *args, **kwargs):
2142+ return super(LoopCreateView, self).dispatch(*args, **kwargs)
2143+
2144+ def get_context_data(self, **kwargs):
2145+ """Get the context data passed to template."""
2146+ context = super(LoopCreateView, self).get_context_data(**kwargs)
2147+ context['request'] = self.request
2148+ return context
2149+
2150+ def get_success_url(self):
2151+ """Return the URL when the form is valid."""
2152+ return reverse(self.reverse_url, args=[self.object.name])
2153
2154=== added file 'dashboard/frontend/views/loop_detail_view.py'
2155--- dashboard/frontend/views/loop_detail_view.py 1970-01-01 00:00:00 +0000
2156+++ dashboard/frontend/views/loop_detail_view.py 2012-09-11 14:48:19 +0000
2157@@ -0,0 +1,46 @@
2158+# Copyright (C) 2012 Linaro
2159+#
2160+# This file is part of linaro-ci-dashboard.
2161+#
2162+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
2163+# it under the terms of the GNU Affero General Public License as published by
2164+# the Free Software Foundation, either version 3 of the License, or
2165+# (at your option) any later version.
2166+#
2167+# linaro-ci-dashboard is distributed in the hope that it will be useful,
2168+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2169+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2170+# GNU Affero General Public License for more details.
2171+#
2172+# You should have received a copy of the GNU Affero General Public License
2173+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
2174+
2175+from django.contrib.auth.decorators import login_required
2176+from django.views.generic.detail import DetailView
2177+from django.utils.decorators import method_decorator
2178+from frontend.models.loop import Loop
2179+
2180+class LoopDetailView(DetailView):
2181+
2182+ # These two fields should *never* be overridden.
2183+ context_object_name = 'loop_detail'
2184+ slug_field = 'name'
2185+ # The following fields have to be overridden in each sub-class.
2186+ model = Loop
2187+ template_name = 'loop_detail.html'
2188+ # This is necessary for each loop to define the URL path for the build
2189+ # view. This must be overridden.
2190+ build_path = 'loop'
2191+
2192+ @method_decorator(login_required)
2193+ def dispatch(self, *args, **kwargs):
2194+ return super(LoopDetailView, self).dispatch(*args, **kwargs)
2195+
2196+ def get_context_data(self, **kwargs):
2197+ '''Get the context data passed to template.'''
2198+ context = super(LoopDetailView, self).get_context_data(**kwargs)
2199+ loop_pointer = self.object.loop_ptr
2200+ context['request'] = self.request
2201+ context['builds'] = loop_pointer.loopbuild_set.all()
2202+ context['build_path'] = self.build_path
2203+ return context
2204
2205=== modified file 'dashboard/frontend/views/loop_get_chainable_view.py'
2206--- dashboard/frontend/views/loop_get_chainable_view.py 2012-09-04 11:56:22 +0000
2207+++ dashboard/frontend/views/loop_get_chainable_view.py 2012-09-11 14:48:19 +0000
2208@@ -25,27 +25,27 @@
2209 from frontend.models.loop import Loop
2210 from lib.model_getter import ModelGetter
2211
2212+
2213 class LoopGetChainableView(ListView):
2214
2215 model = Loop
2216+ template = "list_chainable.html"
2217
2218 @method_decorator(login_required)
2219 def dispatch(self, *args, **kwargs):
2220 return super(LoopGetChainableView, self).dispatch(*args, **kwargs)
2221
2222 def render_to_response(self, context):
2223-
2224 if self.request.is_ajax():
2225-
2226 request_data = self.request.GET
2227
2228 type = request_data.get('type')
2229+ page = request_data.get('page')
2230+
2231 loop_cls = ModelGetter.get_model(type)
2232 chainable_loops_all = loop_cls.get_all_chainable()
2233-
2234 paginator = Paginator(chainable_loops_all, 10)
2235
2236- page = request_data.get('page')
2237 try:
2238 chainable_loops = paginator.page(page)
2239 except PageNotAnInteger:
2240@@ -57,10 +57,8 @@
2241 "chainable_loops": chainable_loops,
2242 }
2243
2244- template = "list_chainable.html"
2245- return render_to_response(template, data,
2246- context_instance=RequestContext(
2247- self.request))
2248-
2249+ return render_to_response(self.template, data,
2250+ context_instance=RequestContext(
2251+ self.request))
2252 else:
2253 raise NotImplementedError
2254
2255=== added file 'dashboard/frontend/views/loop_update_view.py'
2256--- dashboard/frontend/views/loop_update_view.py 1970-01-01 00:00:00 +0000
2257+++ dashboard/frontend/views/loop_update_view.py 2012-09-11 14:48:19 +0000
2258@@ -0,0 +1,55 @@
2259+# Copyright (C) 2012 Linaro
2260+#
2261+# This file is part of linaro-ci-dashboard.
2262+#
2263+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
2264+# it under the terms of the GNU Affero General Public License as published by
2265+# the Free Software Foundation, either version 3 of the License, or
2266+# (at your option) any later version.
2267+#
2268+# linaro-ci-dashboard is distributed in the hope that it will be useful,
2269+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2270+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2271+# GNU Affero General Public License for more details.
2272+#
2273+# You should have received a copy of the GNU Affero General Public License
2274+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
2275+
2276+from django.contrib.auth.decorators import login_required
2277+from django.core.urlresolvers import reverse
2278+from django.views.generic.edit import UpdateView
2279+from django.utils.decorators import method_decorator
2280+from frontend.models.loop import Loop
2281+from frontend.forms.loop_form import LoopForm
2282+
2283+
2284+class LoopUpdateView(UpdateView):
2285+
2286+ # These two fields should *never* be overridden.
2287+ context_object_name = 'loop_update'
2288+ slug_field = 'name'
2289+ # The following fields have to be overridden in each sub-class.
2290+ template_name = 'loop_create.html'
2291+ form_class = LoopForm
2292+ model = Loop
2293+ reverse_url = 'LoopDetail'
2294+
2295+ def form_valid(self, form):
2296+ """This method is called when valid form data has been POSTed.
2297+
2298+ It should return an HttpResponse."""
2299+ return super(LoopUpdateView, self).form_valid(form)
2300+
2301+ @method_decorator(login_required)
2302+ def dispatch(self, *args, **kwargs):
2303+ return super(LoopUpdateView, self).dispatch(*args, **kwargs)
2304+
2305+ def get_context_data(self, **kwargs):
2306+ """Get the context data passed to template."""
2307+ context = super(LoopUpdateView, self).get_context_data(**kwargs)
2308+ context['request'] = self.request
2309+ return context
2310+
2311+ def get_success_url(self):
2312+ """Return the URL when the form is valid."""
2313+ return reverse(self.reverse_url, args=[self.object.name])
2314
2315=== modified file 'dashboard/frontend/widgets/chain_loop_widget.py'
2316--- dashboard/frontend/widgets/chain_loop_widget.py 2012-09-04 11:23:32 +0000
2317+++ dashboard/frontend/widgets/chain_loop_widget.py 2012-09-11 14:48:19 +0000
2318@@ -18,8 +18,6 @@
2319
2320 from django.forms.widgets import HiddenInput
2321 from django.forms.widgets import MultiWidget
2322-from django.template import loader
2323-from django.template.base import Context
2324 from frontend.models.loop import Loop
2325 from frontend.widgets.link_widget import LinkWidget
2326
2327@@ -34,14 +32,14 @@
2328 HiddenInput(attrs=attrs),
2329 LinkWidget(attrs=attrs),
2330 )
2331-
2332 super(ChainLoopWidget, self).__init__(_widgets, attrs)
2333
2334 def decompress(self, value):
2335+ rtn_list = [None, None]
2336 if value:
2337 name = Loop.objects.get(id=value).name
2338- return [value, name]
2339- return [None, None]
2340+ rtn_list = [value, name]
2341+ return rtn_list
2342
2343 def format_output(self, rendered_widgets):
2344 return u''.join(rendered_widgets)
2345
2346=== modified file 'dashboard/jenkinsserver/models/jenkins_server.py'
2347--- dashboard/jenkinsserver/models/jenkins_server.py 2012-08-24 13:00:02 +0000
2348+++ dashboard/jenkinsserver/models/jenkins_server.py 2012-09-11 14:48:19 +0000
2349@@ -102,7 +102,6 @@
2350 "Prepare the config.xml to be used for the job."
2351 # Get generic serialization data.
2352 loop_params = loop.json()
2353-
2354 template_name = loop.__class__.__name__
2355
2356 # We need to handle the CONFIG parameter as a base64 encoded string.

Subscribers

People subscribed via source and target branches