Merge lp:~milo/linaro-ci-dashboard/views-refactoring into lp:linaro-ci-dashboard
- views-refactoring
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stevan Radaković | Approve | ||
Данило Шеган | Pending | ||
Review via email:
|
Commit message
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/
- 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.

Milo Casagrande (milo) wrote : | # |
- 70. By Milo Casagrande
-
Fixed test regression.
- 71. By Milo Casagrande
-
Merged from trunk.

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).

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

Stevan Radaković (stevanr) wrote : | # |
3. happens when you chain AndroidLoop on the AndroidTextFiel

Stevan Radaković (stevanr) wrote : | # |
Also, didn't we agreed to have the Build detail view page as well as part of this WI? :/

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

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/
Small typo on the froNtend application name.
131 raise ValidationError
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.

Stevan Radaković (stevanr) wrote : | # |
Good to go :)
Approve +1
Preview Diff
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 }}: {{ 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 }}: {{ 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 }}: {{ 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. |
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.