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

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

Description of the change

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

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

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

This is moslty all that there is.

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

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

70. By Milo Casagrande

Fixed test regression.

71. By Milo Casagrande

Merged from trunk.

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

Thanks for the extensive refactoring Milo !!1!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The code looks great. Two small comments:

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

Small typo on the froNtend application name.

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

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

72. By Milo Casagrande

Fixed kernel build scheduling.

73. By Milo Casagrande

Fixed name and method signature.

74. By Milo Casagrande

Fixed typos.

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

Good to go :)
Approve +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'HACKING'
--- HACKING 2012-09-07 10:39:47 +0000
+++ HACKING 2012-09-11 14:48:19 +0000
@@ -49,6 +49,66 @@
4949
50Tests should be written and defined inside each sub-application.50Tests should be written and defined inside each sub-application.
5151
52== Class Hierarchy ==
53
54=== Templates & Views ===
55
56The `frontend' base application includes the general HTML Django templates that
57each sub-application should base itself on. These templates are stored in the
58`frontend/templates/' directory.
59
60The `base.html' template is the basic structures for all the HTML pages in the
61application.
62The `login.html' template is the one used for login capabilities.
63The `home.html' template is empty for the moment.
64The `list_chainable' template is used for defining chainability of a loop.
65
66The templates with `create', `update', `detail' and `build' words in them are
67used to implement the views of each loop. These are the templates each loop
68templates should inherit from.
69
70This is the inheritance hierarchy for the HTML templates, in this case for the
71`create' view:
72
73base.html
74 ^
75 |
76 ^
77 |
78loop_create_view.html
79 ^
80 |
81 ^
82 |
83android_build_create_view.html
84
85In a sub-application template, if nothing particular is needed, it is only
86necessary to specify the correct URL name, as defined in each sup-application
87`urls.py' file.
88
89This is the inheritance hierarchy for the Python view classes, always for a
90`create' view:
91
92loop_create_view.py
93 ^
94 |
95 ^
96 |
97android_build_create_view.py
98
99Based on the view Python class, in each sub-application, if nothing special is
100needed, it is only necessary to override the following variables:
101 * template_name
102 * form_class
103 * model
104 * reverse_url or build_path
105
106What should never be overridden is:
107 * context_object_name
108 * slug_field
109
110
111
52= Loop Chaining =112= Loop Chaining =
53113
54Loop chaining in the linaro-ci-dashboard is a mechanism to use output from one114Loop chaining in the linaro-ci-dashboard is a mechanism to use output from one
55115
=== modified file 'dashboard/frontend/android_build/forms/android_loop_form.py'
--- dashboard/frontend/android_build/forms/android_loop_form.py 2012-09-04 11:23:32 +0000
+++ dashboard/frontend/android_build/forms/android_loop_form.py 2012-09-11 14:48:19 +0000
@@ -15,44 +15,33 @@
15# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1717
18from django.forms import CharField18from django.forms import (
19from django.forms import IntegerField19 CharField,
20from django.forms import ModelForm20 HiddenInput,
21from django.forms.widgets import HiddenInput21 ValidationError
22from django.forms import ValidationError22 )
23from frontend.android_build.models.android_loop import (23from frontend.android_build.models.android_loop import (
24 AndroidLoop,24 AndroidLoop,
25 BUILD_ANDROID_TOOLCHAIN_LINARO,25 BUILD_ANDROID_TOOLCHAIN_LINARO,
26 )26 )
27from frontend.widgets.chain_loop_widget import ChainLoopWidget27from frontend.forms.loop_form import LoopForm
2828
2929
30class AndroidLoopForm(ModelForm):30class AndroidLoopForm(LoopForm):
3131 """
32 next_loop = IntegerField(widget=ChainLoopWidget, required=False)32 Form class for the android_build application.
33 """
34 class Meta(LoopForm.Meta):
35 model = AndroidLoop
36
33 type = CharField(widget=HiddenInput, initial=AndroidLoop.__name__,37 type = CharField(widget=HiddenInput, initial=AndroidLoop.__name__,
34 required=False)38 required=False)
3539
36 class Meta:
37 model = AndroidLoop
38 exclude = ('server')
39
40 def save(self, *args, **kwargs):
41 instance = super(AndroidLoopForm, self).save(commit=False)
42
43 if self.data["next_loop_0"]:
44 next_loop = Loop.objects.get(id=self.data["next_loop_0"])
45 instance.next_loop = next_loop
46
47 instance.save()
48
49 return instance
50
51 def clean(self):40 def clean(self):
52 cleaned_data = super(AndroidLoopForm, self).clean()41 cleaned_data = super(AndroidLoopForm, self).clean()
53 if cleaned_data.get('build_type') == BUILD_ANDROID_TOOLCHAIN_LINARO[0]:42 if cleaned_data.get('build_type') == BUILD_ANDROID_TOOLCHAIN_LINARO[0]:
54 if cleaned_data['gcc_url'] == '':43 if cleaned_data['gcc_url'] == '':
55 raise ValidationError("With build of type 'Android Toolchain "44 raise ValidationError("With build of type Android Toolchain "
56 "Linaro, it is necessary to specify "45 "Linaro, it is necessary to specify "
57 "GCC URL.")46 "GCC URL.")
58 return cleaned_data47 return cleaned_data
5948
=== modified file 'dashboard/frontend/android_build/models/android_loop.py'
--- dashboard/frontend/android_build/models/android_loop.py 2012-08-30 21:49:13 +0000
+++ dashboard/frontend/android_build/models/android_loop.py 2012-09-11 14:48:19 +0000
@@ -125,6 +125,17 @@
125 # TODO: Log error.125 # TODO: Log error.
126 pass126 pass
127127
128 def schedule_build(self):128 def schedule_build(self, parameters=None):
129 parameters = {'parameter': [{'delay': '0'}]}129 parameters = {'parameter': [{'delay': '0'}]}
130 return super(self.__class__, self).schedule_build(parameters)130 return super(self.__class__, self).schedule_build(parameters)
131
132 def base64_config(self,
133 include=None,
134 exclude=None,
135 capitalize=False,
136 upper=False):
137 exclude = ['id', 'server', 'loop_ptr', 'next_loop', 'name', 'type']
138 return super(AndroidLoop, self).base64_config(include=include,
139 exclude=exclude,
140 capitalize=capitalize,
141 upper=True)
131142
=== modified file 'dashboard/frontend/android_build/templates/android_loop_create.html'
--- dashboard/frontend/android_build/templates/android_loop_create.html 2012-08-31 15:21:11 +0000
+++ dashboard/frontend/android_build/templates/android_loop_create.html 2012-09-11 14:48:19 +0000
@@ -1,26 +1,10 @@
1{% extends "base.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block content %}3<form action="{% url AndroidLoopCreate %}" method="post">{% csrf_token %}
44{% endblock content_form %}
5{% block create_form %}
6<form name="{{ form_name }}" action="{% url AndroidLoopCreate %}" method="post">
7{% csrf_token %}
8{% endblock create_form %}
9
10{% if form.non_field_errors %}
11 <div class="form_error">
12 {% for err in form.non_field_errors %}
13 <div class="error_message">{{ err }}</div>
14 {% endfor %}
15 </div>
16{% endif %}
17{{ form.as_p }}
18 <div><input type="submit" value="Submit" /></div>
19</form>
20{% endblock %}
215
22{% block scripts %}6{% block scripts %}
23<script type="text/javascript">7{{ block.super }}
24$('#id_lava_submit').attr('onclick', 'disable_enable_lava()');8$('#id_lava_submit').attr('onclick', 'disable_enable_lava()');
259
26disable_enable_lava()10disable_enable_lava()
@@ -36,5 +20,4 @@
36 $('#id_lava_submit_fatal').attr('disabled', disable);20 $('#id_lava_submit_fatal').attr('disabled', disable);
37 $('#id_lava_android_binaries').attr('disabled', disable);21 $('#id_lava_android_binaries').attr('disabled', disable);
38}22}
39</script>23{% endblock scripts %}
40{% endblock %}
4124
=== modified file 'dashboard/frontend/android_build/templates/android_loop_detail.html'
--- dashboard/frontend/android_build/templates/android_loop_detail.html 2012-08-27 15:14:06 +0000
+++ dashboard/frontend/android_build/templates/android_loop_detail.html 2012-09-11 14:48:19 +0000
@@ -1,48 +1,7 @@
1{% extends "base.html" %}1{% extends "loop_detail.html" %}
2{% load dashboard_extras %}
3
4{% block breadcrumbs %}
5<div><a href="{% url Index %}">Index</a></div>
6{% endblock %}
7
8{% block content %}
9 <h2>Loop {{ android_loop_detail.name }}</h2>
10
11 <h4>Details</h4>
12 {% for instance in data %}
13 {% for field, value in instance.fields.items %}
14 <div>
15 {{ android_loop_detail|verbose_name:field }}: {{ value }}
16 </div>
17 {% endfor %}
18 {% endfor %}
19
20 <div>
21 <button type="button" id="update_button">Update loop configuration</button>
22 </div>
23
24 <h4>Builds</h4>
25 <div id="builds">
26 {% for build in builds %}
27 {% include "build.html" %}
28 {% endfor %}
29 </div>
30
31 <div>
32 <button type="button" id="schedule_button">Schedule new build</button>
33 </div>
34{% endblock %}
35
36{% block scripts %}2{% block scripts %}
37<script type="text/javascript">3{{ block.super }}
38$("#schedule_button").click(function() {
39 $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/android/build/{{ android_loop_detail.name }}/", function(data) {
40 $(data).prependTo($("#builds"));
41 });
42});
43
44$("#update_button").click(function() {4$("#update_button").click(function() {
45 window.location.href = "{% url AndroidLoopUpdate android_loop_detail.name %}";5 window.location.href = "{% url AndroidLoopUpdate loop_detail.name %}";
46});6});
47</script>7{% endblock scripts %}
48{% endblock %}
498
=== modified file 'dashboard/frontend/android_build/templates/android_loop_update.html'
--- dashboard/frontend/android_build/templates/android_loop_update.html 2012-08-14 12:07:33 +0000
+++ dashboard/frontend/android_build/templates/android_loop_update.html 2012-09-11 14:48:19 +0000
@@ -1,6 +1,8 @@
1{% extends "android_loop_create.html" %}1{% extends "android_loop_create.html" %}
2
3{% block create_form %}
4 <form name="{{ form_name }}" action="{% url AndroidLoopUpdate android_loop_update.name %}" method="post">
5 {% csrf_token %}
6{% endblock create_form %}
7\ No newline at end of file2\ No newline at end of file
3{% comment %}
4We inherit from android_loop_create.html in this case, since we need a small
5JavaScript that has been written in the android_loop_create.html template.
6{% endcomment %}
7{% block content_form %}
8<form action="{% url AndroidLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
9{% endblock content_form %}
810
=== modified file 'dashboard/frontend/android_build/tests/test_android_build_clientresponse.py'
--- dashboard/frontend/android_build/tests/test_android_build_clientresponse.py 2012-09-04 11:23:32 +0000
+++ dashboard/frontend/android_build/tests/test_android_build_clientresponse.py 2012-09-11 14:48:19 +0000
@@ -42,6 +42,7 @@
42 template_names = [template.name for template in42 template_names = [template.name for template in
43 response.templates]43 response.templates]
44 self.assertEquals(template_names, ["android_loop_create.html",44 self.assertEquals(template_names, ["android_loop_create.html",
45 "loop_create.html",
45 "base.html",46 "base.html",
46 "login.html",47 "login.html",
47 ])48 ])
@@ -66,6 +67,7 @@
66 template_names = [template.name for template in67 template_names = [template.name for template in
67 response.templates]68 response.templates]
68 self.assertEquals(template_names, ["android_loop_detail.html",69 self.assertEquals(template_names, ["android_loop_detail.html",
70 "loop_detail.html",
69 "base.html",71 "base.html",
70 "login.html",72 "login.html",
71 ])73 ])
7274
=== modified file 'dashboard/frontend/android_build/tests/test_android_build_views.py'
--- dashboard/frontend/android_build/tests/test_android_build_views.py 2012-08-23 10:17:07 +0000
+++ dashboard/frontend/android_build/tests/test_android_build_views.py 2012-09-11 14:48:19 +0000
@@ -41,7 +41,6 @@
41 context = AndroidLoopCreateView.get_context_data(41 context = AndroidLoopCreateView.get_context_data(
42 android_loop_create_view)42 android_loop_create_view)
43 self.assertEqual(context['request'], "some request data")43 self.assertEqual(context['request'], "some request data")
44 self.assertEqual(context['form_name'], AndroidLoopCreateView.form_name)
4544
46 def test_detail_view_get_context_data(self):45 def test_detail_view_get_context_data(self):
47 android_loop_detail_view = AndroidLoopDetailView()46 android_loop_detail_view = AndroidLoopDetailView()
@@ -51,7 +50,6 @@
51 android_loop_detail_view)50 android_loop_detail_view)
52 self.assertEqual(context['request'], "some request data")51 self.assertEqual(context['request'], "some request data")
53 self.assertEqual(context['builds'].__class__.__name__, "MagicMock")52 self.assertEqual(context['builds'].__class__.__name__, "MagicMock")
54 self.assertEqual(context['data'].__class__.__name__, "list")
5553
56 def test_update_view_get_context_data(self):54 def test_update_view_get_context_data(self):
57 android_loop_update_view = AndroidLoopUpdateView()55 android_loop_update_view = AndroidLoopUpdateView()
@@ -60,4 +58,3 @@
60 context = AndroidLoopUpdateView.get_context_data(58 context = AndroidLoopUpdateView.get_context_data(
61 android_loop_update_view)59 android_loop_update_view)
62 self.assertEqual(context['request'], "some request data")60 self.assertEqual(context['request'], "some request data")
63 self.assertEqual(context['form_name'], AndroidLoopUpdateView.form_name)
6461
=== modified file 'dashboard/frontend/android_build/views/android_loop_create_view.py'
--- dashboard/frontend/android_build/views/android_loop_create_view.py 2012-08-17 09:25:34 +0000
+++ dashboard/frontend/android_build/views/android_loop_create_view.py 2012-09-11 14:48:19 +0000
@@ -16,39 +16,14 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1818
19from django.contrib.auth.decorators import login_required
20from django.core.urlresolvers import reverse
21from django.views.generic.edit import CreateView
22from django.utils.decorators import method_decorator
23from frontend.android_build.models.android_loop import AndroidLoop19from frontend.android_build.models.android_loop import AndroidLoop
24from frontend.android_build.forms.android_loop_form import AndroidLoopForm20from frontend.android_build.forms.android_loop_form import AndroidLoopForm
2521from frontend.views.loop_create_view import LoopCreateView
2622
27class AndroidLoopCreateView(CreateView):23
24class AndroidLoopCreateView(LoopCreateView):
2825
29 template_name = "android_loop_create.html"26 template_name = "android_loop_create.html"
30 form_class = AndroidLoopForm27 form_class = AndroidLoopForm
31 model = AndroidLoop28 model = AndroidLoop
32 form_name = 'android_loop'29 reverse_url = 'AndroidLoopDetail'
33
34 def form_valid(self, form):
35 """This method is called when valid form data has been POSTed.
36
37 It should return an HttpResponse."""
38 return super(AndroidLoopCreateView, self).form_valid(form)
39
40 @method_decorator(login_required)
41 def dispatch(self, *args, **kwargs):
42 return super(AndroidLoopCreateView, self).dispatch(*args, **kwargs)
43
44 def get_context_data(self, **kwargs):
45 '''Get the context data passed to template.'''
46 context = super(AndroidLoopCreateView,
47 self).get_context_data(**kwargs)
48 context['request'] = self.request
49 context['form_name'] = self.form_name
50 return context
51
52 def get_success_url(self):
53 '''Return the URL when the form is valid.'''
54 return reverse('AndroidLoopDetail', args=[self.object.name])
5530
=== modified file 'dashboard/frontend/android_build/views/android_loop_detail_view.py'
--- dashboard/frontend/android_build/views/android_loop_detail_view.py 2012-08-24 12:35:00 +0000
+++ dashboard/frontend/android_build/views/android_loop_detail_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,37 +15,12 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.views.generic.detail import DetailView
21from django.utils.decorators import method_decorator
22from frontend.models.loop import Loop
23from frontend.android_build.models.android_loop import AndroidLoop18from frontend.android_build.models.android_loop import AndroidLoop
24from django.core import serializers19from frontend.views.loop_detail_view import LoopDetailView
25from itertools import chain20
2621
2722class AndroidLoopDetailView(LoopDetailView):
28class AndroidLoopDetailView(DetailView):
2923
30 model = AndroidLoop24 model = AndroidLoop
31 context_object_name = 'android_loop_detail'
32 slug_field = "name"
33 template_name = "android_loop_detail.html"25 template_name = "android_loop_detail.html"
3426 build_path = 'android'
35 @method_decorator(login_required)
36 def dispatch(self, *args, **kwargs):
37 return super(AndroidLoopDetailView, self).dispatch(*args, **kwargs)
38
39 def get_context_data(self, **kwargs):
40 '''Get the context data passed to template.'''
41 context = super(AndroidLoopDetailView,
42 self).get_context_data(**kwargs)
43 context['request'] = self.request
44 context['builds'] = self.object.loop_ptr.loopbuild_set.all()
45
46 # Need to "merge" the two queryset for all the fields to appear
47 querysets = list(chain(AndroidLoop.objects.filter(
48 pk=self.object.loop_ptr.id),
49 Loop.objects.filter(pk=self.object.loop_ptr.id)))
50 context['data'] = serializers.serialize("python", querysets)
51
52 return context
5327
=== modified file 'dashboard/frontend/android_build/views/android_loop_update_view.py'
--- dashboard/frontend/android_build/views/android_loop_update_view.py 2012-08-20 10:25:27 +0000
+++ dashboard/frontend/android_build/views/android_loop_update_view.py 2012-09-11 14:48:19 +0000
@@ -15,41 +15,14 @@
15# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1717
18from django.contrib.auth.decorators import login_required
19from django.core.urlresolvers import reverse
20from django.views.generic.edit import UpdateView
21from django.utils.decorators import method_decorator
22from frontend.android_build.forms.android_loop_form import AndroidLoopForm18from frontend.android_build.forms.android_loop_form import AndroidLoopForm
23from frontend.android_build.models.android_loop import AndroidLoop19from frontend.android_build.models.android_loop import AndroidLoop
2420from frontend.views.loop_update_view import LoopUpdateView
2521
26class AndroidLoopUpdateView(UpdateView):22
2723class AndroidLoopUpdateView(LoopUpdateView):
28 template_name = "android_loop_update.html"24
25 template_name = 'android_loop_update.html'
29 form_class = AndroidLoopForm26 form_class = AndroidLoopForm
30 context_object_name = 'android_loop_update'
31 model = AndroidLoop27 model = AndroidLoop
32 slug_field = "name"28 reverse_url = 'AndroidLoopDetail'
33 form_name = 'android_loop'
34
35 def form_valid(self, form):
36 """This method is called when valid form data has been POSTed.
37
38 It should return an HttpResponse."""
39 return super(AndroidLoopUpdateView, self).form_valid(form)
40
41 @method_decorator(login_required)
42 def dispatch(self, *args, **kwargs):
43 return super(AndroidLoopUpdateView, self).dispatch(*args, **kwargs)
44
45 def get_context_data(self, **kwargs):
46 '''Get the context data passed to template.'''
47 context = super(AndroidLoopUpdateView,
48 self).get_context_data(**kwargs)
49 context['request'] = self.request
50 context['form_name'] = self.form_name
51 return context
52
53 def get_success_url(self):
54 '''Return the URL when the form is valid.'''
55 return reverse('AndroidLoopDetail', args=[self.object.name])
5629
=== modified file 'dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py'
--- dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py 2012-09-04 14:29:53 +0000
+++ dashboard/frontend/android_textfield_loop/forms/android_textfield_loop_form.py 2012-09-11 14:48:19 +0000
@@ -15,31 +15,21 @@
15# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1717
18from django.forms import CharField18from django.forms import (
19from django.forms import IntegerField19 CharField,
20from django.forms.widgets import HiddenInput20 HiddenInput,
21from frontend.models.loop import Loop21 )
22from frontend.widgets.chain_loop_widget import ChainLoopWidget
23from frontend.forms.textfield_loop_form import TextFieldLoopForm22from frontend.forms.textfield_loop_form import TextFieldLoopForm
24from frontend.android_textfield_loop.models.android_textfield_loop \23from frontend.android_textfield_loop.models.android_textfield_loop \
25 import AndroidTextFieldLoop24 import AndroidTextFieldLoop
2625
2726
28class AndroidTextFieldLoopForm(TextFieldLoopForm):27class AndroidTextFieldLoopForm(TextFieldLoopForm):
28 """
29 Form for the AndroidTextFieldLoop model.
30 """
29 class Meta(TextFieldLoopForm.Meta):31 class Meta(TextFieldLoopForm.Meta):
30 model = AndroidTextFieldLoop32 model = AndroidTextFieldLoop
3133
32 next_loop = IntegerField(widget=ChainLoopWidget, required=False)
33 type = CharField(widget=HiddenInput, initial=AndroidTextFieldLoop.__name__,34 type = CharField(widget=HiddenInput, initial=AndroidTextFieldLoop.__name__,
34 required=False)35 required=False)
35
36 def save(self, *args, **kwargs):
37 instance = super(AndroidTextFieldLoopForm, self).save(commit=False)
38
39 if self.data["next_loop_0"]:
40 next_loop = Loop.objects.get(id=self.data["next_loop_0"])
41 instance.next_loop = next_loop
42
43 instance.save()
44
45 return instance
4636
=== modified file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html'
--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html 2012-09-06 08:31:57 +0000
+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_create.html 2012-09-11 14:48:19 +0000
@@ -1,12 +1,4 @@
1{% extends "base.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block content %}3<form action="{% url AndroidTextFieldLoopCreate %}" method="post">{% csrf_token %}
44{% endblock content_form %}
5{% block create_form %}
6 <form name="{{ form_name }}" action="{% url AndroidTextFieldLoopCreate %}" method="post">
7 {% csrf_token %}
8{% endblock create_form %}
9{{ form.as_p }}
10 <div><input type="submit" value="Submit" /></div>
11 </form>
12{% endblock %}
135
=== modified file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html'
--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html 2012-08-30 15:46:41 +0000
+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_detail.html 2012-09-11 14:48:19 +0000
@@ -1,42 +1,7 @@
1{% extends "base.html" %}1{% extends "textfield_loop_detail.html" %}
2
3{% block breadcrumbs %}
4 <div><a href="{% url Index %}">Index</a></div>
5{% endblock %}
6
7{% block content %}
8 <h2>Loop {{ android_loop_detail.name }}</h2>
9
10 <h4>Details</h4>
11 <div>
12 {{ android_loop_detail.values }}
13 </div>
14 <div>
15 <button type="button" id="update_button">Update loop configuration</button>
16 </div>
17
18 <h4>Builds</h4>
19 <div id="builds">
20 {% for build in builds %}
21 {% include "build.html" %}
22 {% endfor %}
23 </div>
24
25 <div>
26 <button type="button" id="schedule_button">Schedule new build</button>
27 </div>
28{% endblock %}
29
30{% block scripts %}2{% block scripts %}
31 <script type="text/javascript">3{{ block.super }}
32 $("#schedule_button").click(function() {4$("#update_button").click(function() {
33 $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/android-text/build/{{ android_loop_detail.name }}/", function(data) {5 window.location.href = "{% url AndroidTextFieldLoopUpdate loop_detail.name %}";
34 $(data).prependTo($("#builds"));6});
35 });7{% endblock scripts %}
36 });
37
38 $("#update_button").click(function() {
39 window.location.href = "{% url AndroidTextFieldLoopUpdate android_loop_detail.name %}";
40 });
41 </script>
42{% endblock %}
438
=== modified file 'dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html'
--- dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html 2012-08-31 12:25:57 +0000
+++ dashboard/frontend/android_textfield_loop/templates/android_textfield_loop_update.html 2012-09-11 14:48:19 +0000
@@ -1,6 +1,4 @@
1{% extends "android_textfield_loop_create.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block create_form %}3<form action="{% url AndroidTextFieldLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
4 <form name="{{ form_name }}" action="{% url AndroidTextFieldLoopUpdate android_loop_update.name %}" method="post">4{% endblock content_form %}
5 {% csrf_token %}
6{% endblock create_form %}
75
=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py'
--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py 2012-09-04 14:32:49 +0000
+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_clientresponse.py 2012-09-11 14:48:19 +0000
@@ -40,6 +40,7 @@
40 response.templates]40 response.templates]
41 self.assertEquals(template_names,41 self.assertEquals(template_names,
42 ["android_textfield_loop_create.html",42 ["android_textfield_loop_create.html",
43 "loop_create.html",
43 "base.html",44 "base.html",
44 "login.html",45 "login.html",
45 ])46 ])
@@ -65,6 +66,8 @@
65 response.templates]66 response.templates]
66 self.assertEquals(template_names,67 self.assertEquals(template_names,
67 ["android_textfield_loop_detail.html",68 ["android_textfield_loop_detail.html",
69 "textfield_loop_detail.html",
70 "loop_detail.html",
68 "base.html",71 "base.html",
69 "login.html",72 "login.html",
70 ])73 ])
7174
=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py'
--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py 2012-09-07 10:39:47 +0000
+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_loop_model.py 2012-09-11 14:48:19 +0000
@@ -22,7 +22,11 @@
2222
2323
24class AndroidTextFieldLoopModelTest(TestCase):24class AndroidTextFieldLoopModelTest(TestCase):
2525 """
26 Test class for the AndroidTextFieldLoop model. It is mostly used also for
27 tests for the general TextFieldLoop class, since it is an abstract model
28 class and we cannot create object for it.
29 """
26 A_NAME = 'a-build'30 A_NAME = 'a-build'
27 B_NAME = 'b-build'31 B_NAME = 'b-build'
28 VALID_VALUES = u'a=2\nb=3'32 VALID_VALUES = u'a=2\nb=3'
@@ -47,8 +51,8 @@
47 self.assertEqual({}, self.non_valid_android_loop.values_to_dict())51 self.assertEqual({}, self.non_valid_android_loop.values_to_dict())
4852
49 def test_valid_values_correct(self):53 def test_valid_values_correct(self):
50 self.assertEqual(AndroidTextFieldLoop.valid_values(self.VALID_VALUES),54 self.assertEqual((True, self.VALID_LINES),
51 (True, self.VALID_LINES))55 AndroidTextFieldLoop.valid_values(self.VALID_VALUES))
5256
53 def test_valid_values_wrong(self):57 def test_valid_values_wrong(self):
54 self.assertEqual((False, self.NON_VALID_LINES),58 self.assertEqual((False, self.NON_VALID_LINES),
5559
=== modified file 'dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py'
--- dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py 2012-09-03 15:44:47 +0000
+++ dashboard/frontend/android_textfield_loop/tests/test_android_textfield_views.py 2012-09-11 14:48:19 +0000
@@ -37,8 +37,6 @@
37 context = AndroidTextFieldLoopCreateView.get_context_data(37 context = AndroidTextFieldLoopCreateView.get_context_data(
38 android_textfield_create_view)38 android_textfield_create_view)
39 self.assertEqual(context['request'], "some request data")39 self.assertEqual(context['request'], "some request data")
40 self.assertEqual(context['form_name'],
41 AndroidTextFieldLoopCreateView.form_name)
4240
43 def test_detail_view_get_context_data(self):41 def test_detail_view_get_context_data(self):
44 android_textfield_detail_view = AndroidTextFieldLoopDetailView()42 android_textfield_detail_view = AndroidTextFieldLoopDetailView()
@@ -56,5 +54,3 @@
56 context = AndroidTextFieldLoopUpdateView.get_context_data(54 context = AndroidTextFieldLoopUpdateView.get_context_data(
57 android_textfield_update_view)55 android_textfield_update_view)
58 self.assertEqual(context['request'], "some request data")56 self.assertEqual(context['request'], "some request data")
59 self.assertEqual(context['form_name'],
60 AndroidTextFieldLoopUpdateView.form_name)
6157
=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py'
--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py 2012-09-03 14:12:10 +0000
+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_create_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,42 +15,16 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.core.urlresolvers import reverse
21from django.views.generic.edit import CreateView
22from django.utils.decorators import method_decorator
23from frontend.android_textfield_loop.models.android_textfield_loop \18from frontend.android_textfield_loop.models.android_textfield_loop \
24 import AndroidTextFieldLoop19 import AndroidTextFieldLoop
25from frontend.android_textfield_loop.forms.android_textfield_loop_form \20from frontend.android_textfield_loop.forms.android_textfield_loop_form \
26 import AndroidTextFieldLoopForm21 import AndroidTextFieldLoopForm
2722from frontend.views.loop_create_view import LoopCreateView
2823
29class AndroidTextFieldLoopCreateView(CreateView):24
25class AndroidTextFieldLoopCreateView(LoopCreateView):
3026
31 template_name = "android_textfield_loop_create.html"27 template_name = "android_textfield_loop_create.html"
32 form_class = AndroidTextFieldLoopForm28 form_class = AndroidTextFieldLoopForm
33 model = AndroidTextFieldLoop29 model = AndroidTextFieldLoop
34 form_name = 'android_textfield_loop'30 reverse_url = 'AndroidTextFieldLoopDetail'
35
36 def form_valid(self, form):
37 """This method is called when valid form data has been POSTed.
38
39 It should return an HttpResponse."""
40 return super(AndroidTextFieldLoopCreateView, self).form_valid(form)
41
42 @method_decorator(login_required)
43 def dispatch(self, *args, **kwargs):
44 return super(AndroidTextFieldLoopCreateView,
45 self).dispatch(*args, **kwargs)
46
47 def get_context_data(self, **kwargs):
48 '''Get the context data passed to template.'''
49 context = super(AndroidTextFieldLoopCreateView,
50 self).get_context_data(**kwargs)
51 context['request'] = self.request
52 context['form_name'] = self.form_name
53 return context
54
55 def get_success_url(self):
56 '''Return the URL when the form is valid.'''
57 return reverse('AndroidTextFieldLoopDetail', args=[self.object.name])
5831
=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py'
--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py 2012-08-30 15:46:41 +0000
+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_detail_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,30 +15,13 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.views.generic.detail import DetailView
21from django.utils.decorators import method_decorator
22from frontend.android_textfield_loop.models.android_textfield_loop \18from frontend.android_textfield_loop.models.android_textfield_loop \
23 import AndroidTextFieldLoop19 import AndroidTextFieldLoop
2420from frontend.views.loop_detail_view import LoopDetailView
2521
26class AndroidTextFieldLoopDetailView(DetailView):22
23class AndroidTextFieldLoopDetailView(LoopDetailView):
2724
28 model = AndroidTextFieldLoop25 model = AndroidTextFieldLoop
29 context_object_name = 'android_loop_detail'26 template_name = 'android_textfield_loop_detail.html'
30 slug_field = "name"27 build_path = 'android-text'
31 template_name = "android_textfield_loop_detail.html"
32
33 @method_decorator(login_required)
34 def dispatch(self, *args, **kwargs):
35 return super(AndroidTextFieldLoopDetailView,
36 self).dispatch(*args, **kwargs)
37
38 def get_context_data(self, **kwargs):
39 '''Get the context data passed to template.'''
40 context = super(AndroidTextFieldLoopDetailView,
41 self).get_context_data(**kwargs)
42 context['request'] = self.request
43 context['builds'] = self.object.loop_ptr.loopbuild_set.all()
44
45 return context
4628
=== modified file 'dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py'
--- dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py 2012-09-03 14:12:10 +0000
+++ dashboard/frontend/android_textfield_loop/views/android_textfield_loop_update_view.py 2012-09-11 14:48:19 +0000
@@ -15,44 +15,16 @@
15# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1717
18from django.contrib.auth.decorators import login_required
19from django.core.urlresolvers import reverse
20from django.views.generic.edit import UpdateView
21from django.utils.decorators import method_decorator
22from frontend.android_textfield_loop.models.android_textfield_loop \18from frontend.android_textfield_loop.models.android_textfield_loop \
23 import AndroidTextFieldLoop19 import AndroidTextFieldLoop
24from frontend.android_textfield_loop.forms.android_textfield_loop_form \20from frontend.android_textfield_loop.forms.android_textfield_loop_form \
25 import AndroidTextFieldLoopForm21 import AndroidTextFieldLoopForm
2622from frontend.views.loop_update_view import LoopUpdateView
2723
28class AndroidTextFieldLoopUpdateView(UpdateView):24
2925class AndroidTextFieldLoopUpdateView(LoopUpdateView):
30 template_name = "android_textfield_loop_update.html"26
27 template_name = 'android_textfield_loop_update.html'
31 form_class = AndroidTextFieldLoopForm28 form_class = AndroidTextFieldLoopForm
32 context_object_name = 'android_loop_update'
33 model = AndroidTextFieldLoop29 model = AndroidTextFieldLoop
34 slug_field = "name"30 reverse_url = 'AndroidTextFieldLoopDetail'
35 form_name = 'android_textfield_loop'
36
37 def form_valid(self, form):
38 """This method is called when valid form data has been POSTed.
39
40 It should return an HttpResponse."""
41 return super(AndroidTextFieldLoopUpdateView, self).form_valid(form)
42
43 @method_decorator(login_required)
44 def dispatch(self, *args, **kwargs):
45 return super(AndroidTextFieldLoopUpdateView,
46 self).dispatch(*args, **kwargs)
47
48 def get_context_data(self, **kwargs):
49 '''Get the context data passed to template.'''
50 context = super(AndroidTextFieldLoopUpdateView,
51 self).get_context_data(**kwargs)
52 context['request'] = self.request
53 context['form_name'] = self.form_name
54 return context
55
56 def get_success_url(self):
57 '''Return the URL when the form is valid.'''
58 return reverse('AndroidTextFieldLoopDetail', args=[self.object.name])
5931
=== added file 'dashboard/frontend/forms/loop_form.py'
--- dashboard/frontend/forms/loop_form.py 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/forms/loop_form.py 2012-09-11 14:48:19 +0000
@@ -0,0 +1,48 @@
1# Copyright (C) 2012 Linaro
2#
3# This file is part of linaro-ci-dashboard.
4#
5# linaro-ci-dashboard is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# linaro-ci-dashboard is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
17
18from django.forms import (
19 CharField,
20 IntegerField,
21 ModelForm,
22 HiddenInput,
23 )
24from frontend.models.loop import Loop
25from frontend.widgets.chain_loop_widget import ChainLoopWidget
26
27
28class LoopForm(ModelForm):
29 """
30 Generic loop form class that all loop should inherit from.
31 """
32 class Meta:
33 model = Loop
34 exclude = ('server')
35
36 next_loop = IntegerField(widget=ChainLoopWidget, required=False)
37 type = CharField(widget=HiddenInput, initial=Loop.__name__,
38 required=False)
39
40 def save(self, *args, **kwargs):
41 instance = super(LoopForm, self).save(commit=False)
42 if self.data["next_loop_0"]:
43 next_loop = Loop.objects.get(id=self.data["next_loop_0"])
44 instance.next_loop = next_loop
45
46 instance.save()
47
48 return instance
049
=== modified file 'dashboard/frontend/forms/textfield_loop_form.py'
--- dashboard/frontend/forms/textfield_loop_form.py 2012-09-03 15:44:21 +0000
+++ dashboard/frontend/forms/textfield_loop_form.py 2012-09-11 14:48:19 +0000
@@ -15,24 +15,30 @@
15# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1717
18from django.forms import ModelForm
19from django.forms import ValidationError18from django.forms import ValidationError
20from frontend.models.textfield_loop import (19from frontend.models.textfield_loop import (
21 TextFieldLoop,20 TextFieldLoop,
22 DEFAULT_DELIMITER,21 DEFAULT_DELIMITER,
23 )22 )
2423
2524from frontend.forms.loop_form import LoopForm
26class TextFieldLoopForm(ModelForm):25
27 class Meta:26
27class TextFieldLoopForm(LoopForm):
28 """
29 General loop form for loops of the textfield kind.
30
31 A textfield loop should inherit from this class, that in turn inherits from
32 the LoopForm one.
33 """
34 class Meta(LoopForm.Meta):
28 model = TextFieldLoop35 model = TextFieldLoop
29 exclude = ('server', 'type')
3036
31 def clean(self):37 def clean(self):
32 cleaned_data = super(TextFieldLoopForm, self).clean()38 cleaned_data = super(TextFieldLoopForm, self).clean()
33 values = cleaned_data.get('values')39 values = cleaned_data.get('values')
34 if values:40 if values:
35 valid , _ = self.instance.valid_values(values)41 valid, _ = self.instance.valid_values(values)
36 if not valid:42 if not valid:
37 raise ValidationError("Wrong delimiter used: please use the "43 raise ValidationError("Wrong delimiter used: please use the "
38 "%s character as delimiter." %44 "%s character as delimiter." %
3945
=== modified file 'dashboard/frontend/integration_loop/forms/integration_loop_form.py'
--- dashboard/frontend/integration_loop/forms/integration_loop_form.py 2012-09-04 11:23:32 +0000
+++ dashboard/frontend/integration_loop/forms/integration_loop_form.py 2012-09-11 14:48:19 +0000
@@ -16,32 +16,20 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1818
19from django.forms import CharField19from django.forms import (
20from django.forms import IntegerField20 CharField,
21from django.forms import ModelForm21 HiddenInput,
22from django.forms.widgets import HiddenInput22 )
23from frontend.forms.loop_form import LoopForm
23from frontend.integration_loop.models.integration_loop import IntegrationLoop24from frontend.integration_loop.models.integration_loop import IntegrationLoop
24from frontend.models.loop import Loop25
25from frontend.widgets.chain_loop_widget import ChainLoopWidget26
2627class IntegrationLoopForm(LoopForm):
2728 """
28class IntegrationLoopForm(ModelForm):29 Form for the IntegrationLoop model.
2930 """
30 next_loop = IntegerField(widget=ChainLoopWidget, required=False)31 class Meta(LoopForm.Meta):
32 model = IntegrationLoop
33
31 type = CharField(widget=HiddenInput, initial=IntegrationLoop.__name__,34 type = CharField(widget=HiddenInput, initial=IntegrationLoop.__name__,
32 required=False)35 required=False)
33
34 class Meta:
35 model = IntegrationLoop
36 exclude = ('server')
37
38 def save(self, *args, **kwargs):
39 instance = super(IntegrationLoopForm, self).save(commit=False)
40
41 if self.data["next_loop_0"]:
42 next_loop = Loop.objects.get(id=self.data["next_loop_0"])
43 instance.next_loop = next_loop
44
45 instance.save()
46
47 return instance
4836
=== modified file 'dashboard/frontend/integration_loop/templates/integration_loop_create.html'
--- dashboard/frontend/integration_loop/templates/integration_loop_create.html 2012-08-16 15:12:10 +0000
+++ dashboard/frontend/integration_loop/templates/integration_loop_create.html 2012-09-11 14:48:19 +0000
@@ -1,8 +1,4 @@
1{% extends "base.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block content %}
4<form action="{% url IntegrationLoopCreate %}" method="post">{% csrf_token %}3<form action="{% url IntegrationLoopCreate %}" method="post">{% csrf_token %}
5{{ form.as_p }}4{% endblock content_form %}
6<div><input type="submit" value="Submit" /></div>
7</form>
8{% endblock %}
95
=== modified file 'dashboard/frontend/integration_loop/templates/integration_loop_detail.html'
--- dashboard/frontend/integration_loop/templates/integration_loop_detail.html 2012-08-31 13:34:41 +0000
+++ dashboard/frontend/integration_loop/templates/integration_loop_detail.html 2012-09-11 14:48:19 +0000
@@ -1,54 +1,7 @@
1{% extends "base.html" %}1{% extends "loop_detail.html" %}
2
3{% block breadcrumbs %}
4<div>
5<a href="{% url Index %}">Index</a>
6</div>
7{% endblock %}
8
9{% block content %}
10 <h2>Loop {{ integration_loop_detail.name }}</h2>
11
12 <h4>Details</h4>
13 <div>
14 Next loop in chain: {{ integration_loop_detail.next_loop.name }}
15 </div>
16 <div>
17 Branch: {{ integration_loop_detail.branch }}
18 </div>
19 <div>
20 Pre-command: {{ integration_loop_detail.precommand }}
21 </div>
22 <div>
23 Test Command: {{ integration_loop_detail.command }}
24 </div>
25
26 <div>
27 <button type="button" id="update_button">Update loop configuration</button>
28 </div>
29
30 <h4>Builds</h4>
31 <div id="builds">
32 {% for build in builds %}
33 {% include "build.html" %}
34 {% endfor %}
35 </div>
36
37 <div>
38 <button type="button" id="schedule_button">Schedule new build</button>
39 </div>
40{% endblock %}
41
42{% block scripts %}2{% block scripts %}
43<script type="text/javascript">3{{ block.super }}
44$("#schedule_button").click(function() {
45 $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/integration_loop/build/{{ integration_loop_detail.name }}/", function(data) {
46 $(data).prependTo($("#builds"));
47 });
48});
49
50$("#update_button").click(function() {4$("#update_button").click(function() {
51 window.location.href = "{% url IntegrationLoopUpdate integration_loop_detail.name %}";5 window.location.href = "{% url IntegrationLoopUpdate loop_detail.name %}";
52});6});
53</script>7{% endblock scripts %}
54{% endblock %}
558
=== modified file 'dashboard/frontend/integration_loop/templates/integration_loop_update.html'
--- dashboard/frontend/integration_loop/templates/integration_loop_update.html 2012-08-16 15:12:10 +0000
+++ dashboard/frontend/integration_loop/templates/integration_loop_update.html 2012-09-11 14:48:19 +0000
@@ -1,8 +1,4 @@
1{% extends "base.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block content %}3<form action="{% url IntegrationLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
4<form action="{% url IntegrationLoopUpdate integration_loop_update.name %}" method="post">{% csrf_token %}4{% endblock content_form %}
5{{ form.as_p }}
6<div><input type="submit" value="Submit" /></div>
7</form>
8{% endblock %}
95
=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_create_view.py'
--- dashboard/frontend/integration_loop/views/integration_loop_create_view.py 2012-08-16 15:12:10 +0000
+++ dashboard/frontend/integration_loop/views/integration_loop_create_view.py 2012-09-11 14:48:19 +0000
@@ -16,39 +16,15 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1818
19from django.contrib.auth.decorators import login_required
20from django.core.urlresolvers import reverse
21from django.views.generic.edit import CreateView
22from django.utils.decorators import method_decorator
23from frontend.integration_loop.forms.integration_loop_form \19from frontend.integration_loop.forms.integration_loop_form \
24 import IntegrationLoopForm20 import IntegrationLoopForm
25from frontend.integration_loop.models.integration_loop import IntegrationLoop21from frontend.integration_loop.models.integration_loop import IntegrationLoop
2622from frontend.views.loop_create_view import LoopCreateView
2723
28class IntegrationLoopCreateView(CreateView):24
25class IntegrationLoopCreateView(LoopCreateView):
2926
30 template_name = "integration_loop_create.html"27 template_name = "integration_loop_create.html"
31 form_class = IntegrationLoopForm28 form_class = IntegrationLoopForm
32 model = IntegrationLoop29 model = IntegrationLoop
3330 reverse_url = 'IntegrationLoopDetail'
34 def form_valid(self, form):
35 """This method is called when valid form data has been POSTed.
36
37 It should return an HttpResponse."""
38
39 return super(IntegrationLoopCreateView, self).form_valid(form)
40
41 @method_decorator(login_required)
42 def dispatch(self, *args, **kwargs):
43 return super(IntegrationLoopCreateView, self).dispatch(*args, **kwargs)
44
45 def get_context_data(self, **kwargs):
46 '''Get the context data passed to template.'''
47 context = super(IntegrationLoopCreateView,
48 self).get_context_data(**kwargs)
49 context['request'] = self.request
50 return context
51
52 def get_success_url(self):
53 '''Return the URL when the form is valid.'''
54 return reverse('IntegrationLoopDetail', args=[self.object.name])
5531
=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_detail_view.py'
--- dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-08-16 15:12:10 +0000
+++ dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,27 +15,12 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.views.generic.detail import DetailView
21from django.utils.decorators import method_decorator
22from frontend.integration_loop.models.integration_loop import IntegrationLoop18from frontend.integration_loop.models.integration_loop import IntegrationLoop
2319from frontend.views.loop_detail_view import LoopDetailView
2420
25class IntegrationLoopDetailView(DetailView):21
22class IntegrationLoopDetailView(LoopDetailView):
2623
27 model = IntegrationLoop24 model = IntegrationLoop
28 context_object_name = 'integration_loop_detail'
29 slug_field = "name"
30 template_name = "integration_loop_detail.html"25 template_name = "integration_loop_detail.html"
3126 build_path = 'integration_loop'
32 @method_decorator(login_required)
33 def dispatch(self, *args, **kwargs):
34 return super(IntegrationLoopDetailView, self).dispatch(*args, **kwargs)
35
36 def get_context_data(self, **kwargs):
37 '''Get the context data passed to template.'''
38 context = super(IntegrationLoopDetailView,
39 self).get_context_data(**kwargs)
40 context['request'] = self.request
41 context['builds'] = self.object.loop_ptr.loopbuild_set.all()
42 return context
4327
=== modified file 'dashboard/frontend/integration_loop/views/integration_loop_update_view.py'
--- dashboard/frontend/integration_loop/views/integration_loop_update_view.py 2012-08-16 15:12:10 +0000
+++ dashboard/frontend/integration_loop/views/integration_loop_update_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,41 +15,15 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.core.urlresolvers import reverse
21from django.views.generic.edit import UpdateView
22from django.utils.decorators import method_decorator
23from frontend.integration_loop.forms.integration_loop_form \18from frontend.integration_loop.forms.integration_loop_form \
24 import IntegrationLoopForm19 import IntegrationLoopForm
25from frontend.integration_loop.models.integration_loop import IntegrationLoop20from frontend.integration_loop.models.integration_loop import IntegrationLoop
2621from frontend.views.loop_update_view import LoopUpdateView
2722
28class IntegrationLoopUpdateView(UpdateView):23
2924class IntegrationLoopUpdateView(LoopUpdateView):
30 template_name = "integration_loop_update.html"25
26 template_name = 'integration_loop_update.html'
31 form_class = IntegrationLoopForm27 form_class = IntegrationLoopForm
32 context_object_name = 'integration_loop_update'
33 model = IntegrationLoop28 model = IntegrationLoop
34 slug_field = "name"29 reverse_url = 'IntegrationLoopDetail'
35
36 def form_valid(self, form):
37 """This method is called when valid form data has been POSTed.
38
39 It should return an HttpResponse."""
40
41 return super(IntegrationLoopUpdateView, self).form_valid(form)
42
43 @method_decorator(login_required)
44 def dispatch(self, *args, **kwargs):
45 return super(IntegrationLoopUpdateView, self).dispatch(*args, **kwargs)
46
47 def get_context_data(self, **kwargs):
48 '''Get the context data passed to template.'''
49 context = super(IntegrationLoopUpdateView,
50 self).get_context_data(**kwargs)
51 context['request'] = self.request
52 return context
53
54 def get_success_url(self):
55 '''Return the URL when the form is valid.'''
56 return reverse('IntegrationLoopDetail', args=[self.object.name])
5730
=== modified file 'dashboard/frontend/kernel_build/forms/kernel_loop_form.py'
--- dashboard/frontend/kernel_build/forms/kernel_loop_form.py 2012-09-04 11:23:32 +0000
+++ dashboard/frontend/kernel_build/forms/kernel_loop_form.py 2012-09-11 14:48:19 +0000
@@ -16,31 +16,20 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1818
19from django.forms import CharField19from django.forms import (
20from django.forms import IntegerField20 CharField,
21from django.forms import ModelForm21 HiddenInput,
22from django.forms.widgets import HiddenInput22)
23from frontend.forms.loop_form import LoopForm
23from frontend.kernel_build.models.kernel_loop import KernelLoop24from frontend.kernel_build.models.kernel_loop import KernelLoop
24from frontend.widgets.chain_loop_widget import ChainLoopWidget25
2526
2627class KernelLoopForm(LoopForm):
27class KernelLoopForm(ModelForm):28 """
2829 Form class for the KernelLoop model.
29 next_loop = IntegerField(widget=ChainLoopWidget, required=False)30 """
31 class Meta(LoopForm.Meta):
32 model = KernelLoop
33
30 type = CharField(widget=HiddenInput, initial=KernelLoop.__name__,34 type = CharField(widget=HiddenInput, initial=KernelLoop.__name__,
31 required=False)35 required=False)
32
33 class Meta:
34 model = KernelLoop
35 exclude = ('server')
36
37 def save(self, *args, **kwargs):
38 instance = super(KernelLoopForm, self).save(commit=False)
39
40 if self.data["next_loop_0"]:
41 next_loop = Loop.objects.get(id=self.data["next_loop_0"])
42 instance.next_loop = next_loop
43
44 instance.save()
45
46 return instance
4736
=== modified file 'dashboard/frontend/kernel_build/templates/kernel_loop_create.html'
--- dashboard/frontend/kernel_build/templates/kernel_loop_create.html 2012-08-17 11:58:18 +0000
+++ dashboard/frontend/kernel_build/templates/kernel_loop_create.html 2012-09-11 14:48:19 +0000
@@ -1,8 +1,4 @@
1{% extends "base.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block content %}
4<form action="{% url KernelLoopCreate %}" method="post">{% csrf_token %}3<form action="{% url KernelLoopCreate %}" method="post">{% csrf_token %}
5{{ form.as_p }}4{% endblock content_form %}
6<div><input type="submit" value="Submit" /></div>
7</form>
8{% endblock %}
95
=== modified file 'dashboard/frontend/kernel_build/templates/kernel_loop_detail.html'
--- dashboard/frontend/kernel_build/templates/kernel_loop_detail.html 2012-08-27 15:14:06 +0000
+++ dashboard/frontend/kernel_build/templates/kernel_loop_detail.html 2012-09-11 14:48:19 +0000
@@ -1,49 +1,7 @@
1{% extends "base.html" %}1{% extends "loop_detail.html" %}
2
3{% block breadcrumbs %}
4<div>
5<a href="{% url Index %}">Index</a>
6</div>
7{% endblock %}
8
9{% block content %}
10 <h2>Loop {{ kernel_loop_detail.name }}</h2>
11
12 <h4>Details</h4>
13 <div>
14 <table>
15 {% for k, v in kernel_loop_detail.json.items %}
16 <tr><td>{{ k }}</td><td>{{ v }}</td></tr>
17 {% endfor %}
18 </table>
19 </div>
20
21 <div>
22 <button type="button" id="update_button">Update loop configuration</button>
23 </div>
24
25 <h4>Builds</h4>
26 <div id="builds">
27 {% for build in builds %}
28 {% include "build.html" %}
29 {% endfor %}
30 </div>
31
32 <div>
33 <button type="button" id="schedule_button">Schedule new build</button>
34 </div>
35{% endblock %}
36
37{% block scripts %}2{% block scripts %}
38<script type="text/javascript">3{{ block.super }}
39$("#schedule_button").click(function() {
40 $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/kernel/build/{{ kernel_loop_detail.name }}/", function(data) {
41 $(data).prependTo($("#builds"));
42 });
43});
44
45$("#update_button").click(function() {4$("#update_button").click(function() {
46 window.location.href = "{% url KernelLoopUpdate kernel_loop_detail.name %}";5 window.location.href = "{% url KernelLoopUpdate loop_detail.name %}";
47});6});
48</script>7{% endblock scripts %}
49{% endblock %}
508
=== modified file 'dashboard/frontend/kernel_build/templates/kernel_loop_update.html'
--- dashboard/frontend/kernel_build/templates/kernel_loop_update.html 2012-08-20 20:12:55 +0000
+++ dashboard/frontend/kernel_build/templates/kernel_loop_update.html 2012-09-11 14:48:19 +0000
@@ -1,8 +1,4 @@
1{% extends "base.html" %}1{% extends "loop_create.html" %}
22{% block content_form %}
3{% block content %}3<form action="{% url KernelLoopUpdate loop_update.name %}" method="post">{% csrf_token %}
4<form action="{% url KernelLoopUpdate kernel_loop_update.name %}" method="post">{% csrf_token %}4{% endblock content_form %}
5{{ form.as_p }}
6<div><input type="submit" value="Submit" /></div>
7</form>
8{% endblock %}
95
=== modified file 'dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py'
--- dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py 2012-09-04 14:32:49 +0000
+++ dashboard/frontend/kernel_build/tests/test_kernel_build_clientresponse.py 2012-09-11 14:48:19 +0000
@@ -46,6 +46,7 @@
46 template_names = [template.name for template in46 template_names = [template.name for template in
47 response.templates]47 response.templates]
48 self.assertEquals(template_names, ["kernel_loop_create.html",48 self.assertEquals(template_names, ["kernel_loop_create.html",
49 "loop_create.html",
49 "base.html",50 "base.html",
50 "login.html",51 "login.html",
51 ])52 ])
@@ -70,6 +71,7 @@
70 template_names = [template.name for template in71 template_names = [template.name for template in
71 response.templates]72 response.templates]
72 self.assertEquals(template_names, ["kernel_loop_detail.html",73 self.assertEquals(template_names, ["kernel_loop_detail.html",
74 "loop_detail.html",
73 "base.html",75 "base.html",
74 "login.html",76 "login.html",
75 ])77 ])
7678
=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_create_view.py'
--- dashboard/frontend/kernel_build/views/kernel_loop_create_view.py 2012-08-20 20:12:55 +0000
+++ dashboard/frontend/kernel_build/views/kernel_loop_create_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,39 +15,15 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.core.urlresolvers import reverse
21from django.views.generic.edit import CreateView
22from django.utils.decorators import method_decorator
23from frontend.kernel_build.forms.kernel_loop_form \18from frontend.kernel_build.forms.kernel_loop_form \
24 import KernelLoopForm19 import KernelLoopForm
25from frontend.kernel_build.models.kernel_loop import KernelLoop20from frontend.kernel_build.models.kernel_loop import KernelLoop
2621from frontend.views.loop_create_view import LoopCreateView
2722
28class KernelLoopCreateView(CreateView):23
24class KernelLoopCreateView(LoopCreateView):
2925
30 template_name = "kernel_loop_create.html"26 template_name = "kernel_loop_create.html"
31 form_class = KernelLoopForm27 form_class = KernelLoopForm
32 model = KernelLoop28 model = KernelLoop
3329 reverse_url = 'KernelLoopDetail'
34 def form_valid(self, form):
35 """This method is called when valid form data has been POSTed.
36
37 It should return an HttpResponse."""
38
39 return super(KernelLoopCreateView, self).form_valid(form)
40
41 @method_decorator(login_required)
42 def dispatch(self, *args, **kwargs):
43 return super(KernelLoopCreateView, self).dispatch(*args, **kwargs)
44
45 def get_context_data(self, **kwargs):
46 '''Get the context data passed to template.'''
47 context = super(KernelLoopCreateView,
48 self).get_context_data(**kwargs)
49 context['request'] = self.request
50 return context
51
52 def get_success_url(self):
53 '''Return the URL when the form is valid.'''
54 return reverse('KernelLoopDetail', args=[self.object.name])
5530
=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py'
--- dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py 2012-08-20 20:12:55 +0000
+++ dashboard/frontend/kernel_build/views/kernel_loop_detail_view.py 2012-09-11 14:48:19 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2# Copyright (C) 2012 Linaro1# Copyright (C) 2012 Linaro
3#2#
4# This file is part of linaro-ci-dashboard.3# This file is part of linaro-ci-dashboard.
@@ -16,27 +15,12 @@
16# You should have received a copy of the GNU Affero General Public License15# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1817
19from django.contrib.auth.decorators import login_required
20from django.views.generic.detail import DetailView
21from django.utils.decorators import method_decorator
22from frontend.kernel_build.models.kernel_loop import KernelLoop18from frontend.kernel_build.models.kernel_loop import KernelLoop
2319from frontend.views.loop_detail_view import LoopDetailView
2420
25class KernelLoopDetailView(DetailView):21
22class KernelLoopDetailView(LoopDetailView):
2623
27 model = KernelLoop24 model = KernelLoop
28 context_object_name = 'kernel_loop_detail'25 template_name = 'kernel_loop_detail.html'
29 slug_field = "name"26 build_path = 'kernel'
30 template_name = "kernel_loop_detail.html"
31
32 @method_decorator(login_required)
33 def dispatch(self, *args, **kwargs):
34 return super(KernelLoopDetailView, self).dispatch(*args, **kwargs)
35
36 def get_context_data(self, **kwargs):
37 '''Get the context data passed to template.'''
38 context = super(KernelLoopDetailView,
39 self).get_context_data(**kwargs)
40 context['request'] = self.request
41 context['builds'] = self.object.loop_ptr.loopbuild_set.all()
42 return context
4327
=== modified file 'dashboard/frontend/kernel_build/views/kernel_loop_update_view.py'
--- dashboard/frontend/kernel_build/views/kernel_loop_update_view.py 2012-08-20 20:12:55 +0000
+++ dashboard/frontend/kernel_build/views/kernel_loop_update_view.py 2012-09-11 14:48:19 +0000
@@ -16,41 +16,15 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.17# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
1818
19from django.contrib.auth.decorators import login_required
20from django.core.urlresolvers import reverse
21from django.views.generic.edit import UpdateView
22from django.utils.decorators import method_decorator
23from frontend.kernel_build.forms.kernel_loop_form \19from frontend.kernel_build.forms.kernel_loop_form \
24 import KernelLoopForm20 import KernelLoopForm
25from frontend.kernel_build.models.kernel_loop import KernelLoop21from frontend.kernel_build.models.kernel_loop import KernelLoop
2622from frontend.views.loop_update_view import LoopUpdateView
2723
28class KernelLoopUpdateView(UpdateView):24
2925class KernelLoopUpdateView(LoopUpdateView):
30 template_name = "kernel_loop_update.html"26
27 template_name = 'kernel_loop_update.html'
31 form_class = KernelLoopForm28 form_class = KernelLoopForm
32 context_object_name = 'kernel_loop_update'
33 model = KernelLoop29 model = KernelLoop
34 slug_field = "name"30 reverse_url = 'KernelLoopDetail'
35
36 def form_valid(self, form):
37 """This method is called when valid form data has been POSTed.
38
39 It should return an HttpResponse."""
40
41 return super(KernelLoopUpdateView, self).form_valid(form)
42
43 @method_decorator(login_required)
44 def dispatch(self, *args, **kwargs):
45 return super(KernelLoopUpdateView, self).dispatch(*args, **kwargs)
46
47 def get_context_data(self, **kwargs):
48 '''Get the context data passed to template.'''
49 context = super(KernelLoopUpdateView,
50 self).get_context_data(**kwargs)
51 context['request'] = self.request
52 return context
53
54 def get_success_url(self):
55 '''Return the URL when the form is valid.'''
56 return reverse('KernelLoopDetail', args=[self.object.name])
5731
=== modified file 'dashboard/frontend/models/loop.py'
--- dashboard/frontend/models/loop.py 2012-09-10 15:23:53 +0000
+++ dashboard/frontend/models/loop.py 2012-09-11 14:48:19 +0000
@@ -31,7 +31,7 @@
31 default=None,31 default=None,
32 null=True,32 null=True,
33 on_delete=models.SET_NULL)33 on_delete=models.SET_NULL)
34 type = models.CharField(max_length=50, editable=False)34 type = models.CharField(max_length=50, editable=False, blank=True)
35 is_restricted = models.BooleanField(default=False,35 is_restricted = models.BooleanField(default=False,
36 verbose_name='Build is restricted')36 verbose_name='Build is restricted')
37 is_official = models.BooleanField(default=False,37 is_official = models.BooleanField(default=False,
@@ -41,11 +41,6 @@
41 related_name='previous_loop',41 related_name='previous_loop',
42 verbose_name='Next loop in chain')42 verbose_name='Next loop in chain')
4343
44 # List of fields to be excluded by default from the base64 encoded config.
45 # If a subclass needs different fields, it is necessary to override this
46 # variable.
47 EXCLUDE_LIST = ['id', 'server', 'loop_ptr', 'next_loop', 'name', 'type']
48
49 def __init__(self, *args, **kwargs):44 def __init__(self, *args, **kwargs):
50 self.log = Logger.getClassLogger(self)45 self.log = Logger.getClassLogger(self)
51 super(Loop, self).__init__(*args, **kwargs)46 super(Loop, self).__init__(*args, **kwargs)
@@ -125,16 +120,32 @@
125 return []120 return []
126121
127 def __repr__(self):122 def __repr__(self):
128 return "%s(id=%s, ...)" % (self.__class__.__name__, self.id)123 return "%s(id=%s)" % (self.__class__.__name__, self.id)
129124
130 def base64_config(self):125 def base64_config(self,
126 include=None,
127 exclude=None,
128 capitalize=False,
129 upper=False):
131 """ Return loop configuration as base64 encoded string.130 """ Return loop configuration as base64 encoded string.
131
132 :param include: the list of fields, as string, to get.
133 :type include list
134 :param exclude: the list of fields, as string, to exclude.
135 :type exclude list
136 :param capitalize: whether the field name should be capitalized (the
137 first letter should be a capital one).
138 :type capitalize bool
139 :param upper: whether the field name should be all capital letter.
140 :type upper bool
141 :return A base64 encoded string composed of 'key=value'
132 """142 """
133 text_config = u''143 text_config = u''
134 for field in self._meta.fields:144 for field in self._get_wanted_fields(include=include,
135 if not field.name in self.EXCLUDE_LIST:145 exclude=exclude,
136 text_config += u'%s=%s\n' % (field.name.upper(),146 capitalize=capitalize,
137 field.value_to_string(self))147 upper=upper):
148 text_config += u'%s=%s\n' % (field[0], str(field[1]))
138 return base64.b64encode(text_config)149 return base64.b64encode(text_config)
139150
140 def get_child_object(self):151 def get_child_object(self):
@@ -150,6 +161,87 @@
150 result.extend(Loop.objects.filter(type=loop_type))161 result.extend(Loop.objects.filter(type=loop_type))
151 return result162 return result
152163
164 def _get_wanted_fields(self,
165 include=None,
166 exclude=None,
167 verbose_name=False,
168 capitalize=False,
169 upper=False):
170 """
171 Gets the wanted fields, as a list of tuples with
172 (field.name, field.value). By default retrieves all the fields
173 associated with the loop instance.
174
175 There is some control over the results: it is possible to retrieve only
176 a subset of the fields, to retrieve the verbose name associated with
177 the fields, and either to capitalize or make all capital letters
178 the name.
179
180 The parameters used to retrieve a subset of fields are 'include' and
181 'exclude'. If both are used, 'exclude' has precedence, meaning that if
182 a field is specified both in 'exclude' and in 'include', that result
183 will not include that field.
184
185 :param include: the list of fields, as string, to get.
186 :type include list
187 :param exclude: the list of fields, as string, to exclude.
188 :type exclude list
189 :param verbose_name: whether to retrieve the verbose name associated
190 with the field. Default False.
191 :type verbose_name bool
192 :param capitalize: whether the field name should be capitalized (the
193 first letter should be a capital one).
194 :type capitalize bool
195 :param upper: whether the field name should be all capital letter.
196 :type upper bool
197 :return a list of tuples, in which each tuple consists of
198 (field.name, field.value).
199 """
200 wanted_fields = []
201 for field in self._meta.fields:
202 # First check if field is excluded, in case both 'include'
203 # and 'exclude' are passed. Exclude wins.
204 if exclude and field.name in exclude:
205 continue
206 if include and not field.name in include:
207 continue
208
209 # Consider OneToOneField, since we do not have the value stored,
210 # we need to retrieve it.
211 value = None
212 if isinstance(field, models.related.OneToOneField):
213 pk = field.value_from_object(self)
214 if pk:
215 value = Loop.objects.get(pk=pk).name
216 else:
217 value = field._get_val_from_obj(self)
218
219 if verbose_name:
220 name = field.verbose_name
221 else:
222 name = field.name
223 if capitalize:
224 name = name.capitalize()
225 if upper:
226 name = name.upper()
227
228 wanted_fields.append((name, value))
229 return wanted_fields
230
231 def get_details(self):
232 """
233 Retrieves the necessary fields for the detail view.
234 This is mostly and probably only needed in the detail HTML template.
235
236 The fields retrieved here are not all the fields in the model.
237
238 :return A list of tuples.
239 """
240 excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr']
241 return self._get_wanted_fields(exclude=excluded_fields,
242 verbose_name=True,
243 capitalize=True)
244
153 def continue_down_the_chain(self, configuration=None):245 def continue_down_the_chain(self, configuration=None):
154 """Invokes the build of the next loop in chain.246 """Invokes the build of the next loop in chain.
155247
156248
=== modified file 'dashboard/frontend/models/textfield_loop.py'
--- dashboard/frontend/models/textfield_loop.py 2012-09-10 12:25:33 +0000
+++ dashboard/frontend/models/textfield_loop.py 2012-09-11 14:48:19 +0000
@@ -83,11 +83,22 @@
8383
84 if valid:84 if valid:
85 for line in lines:85 for line in lines:
86 key, value = line.split(DEFAULT_DELIMITER)86 key, value = self.split_line(line)
87 text_to_dict[key.lower()] = value87 text_to_dict[key] = value
8888
89 return text_to_dict89 return text_to_dict
9090
91 def split_line(self, line):
92 """
93 Splits the line on the default delimiter defined in
94 'DEFAULT_DELIMITER'.
95
96 :param line: the line to split.
97 :type line str
98 return A tuple with (name, value).
99 """
100 return line.split(DEFAULT_DELIMITER)
101
91 @staticmethod102 @staticmethod
92 def valid_values(values):103 def valid_values(values):
93 """104 """
@@ -97,10 +108,11 @@
97 :param values: the string with all the key<>value pairs, separated with108 :param values: the string with all the key<>value pairs, separated with
98 a newline character.109 a newline character.
99 :type values unicode110 :type values unicode
100 :return a boolean for the validity, and the list of lines.111 :return A tuple with the first element a boolean for the validity, the
112 second element the list of all the lines.
101 """113 """
102 valid = True114 valid = True
103 lines = values.splitlines()115 lines = TextFieldLoop._get_all_lines(values)
104 for line in lines:116 for line in lines:
105 if DEFAULT_DELIMITER in line:117 if DEFAULT_DELIMITER in line:
106 continue118 continue
@@ -109,12 +121,21 @@
109 break121 break
110 return valid, lines122 return valid, lines
111123
124 @staticmethod
125 def _get_all_lines(values):
126 """
127 Returns all the lines in the textfield as a single list.
128
129 :return The list of lines in the textfield.
130 """
131 return values.splitlines()
132
112 def values_to_xml(self):133 def values_to_xml(self):
113 """134 """
114 Converts the necessary values into an XML tree.135 Converts the necessary values into an XML tree.
115136
116 :return The XML tree as a string, or an empty string if the inserted137 :return The XML tree as a string, or an empty string if there are no
117 values are not valid.138 valid values.
118 """139 """
119 xml_string = ""140 xml_string = ""
120 valid, lines = self.valid_values(self.values)141 valid, lines = self.valid_values(self.values)
@@ -125,4 +146,25 @@
125 if other_values:146 if other_values:
126 values.update(other_values)147 values.update(other_values)
127 xml_string = DictToXml(values).dict_to_tree()148 xml_string = DictToXml(values).dict_to_tree()
149
128 return xml_string150 return xml_string
151
152 def get_details(self):
153 # We need to override this here, since we want each value inserted in
154 # the textfield to appear on a single line. We skip the field here
155 # and define a separate functions to retrieve the lines from the
156 # HTML template.
157 excluded_fields = ['name', 'server', 'type', 'id', 'loop_ptr',
158 'values']
159 return self._get_wanted_fields(exclude=excluded_fields,
160 verbose_name=True,
161 capitalize=True)
162
163 def get_values(self):
164 """
165 This method is mostly used in the detail view for the textfield loops.
166 It returns the list of single string entered in the textfield.
167
168 :return A list of strings.
169 """
170 return self._get_all_lines(self.values)
129171
=== added file 'dashboard/frontend/templates/__init__.py'
=== modified file 'dashboard/frontend/templates/base.html'
--- dashboard/frontend/templates/base.html 2012-09-03 12:04:52 +0000
+++ dashboard/frontend/templates/base.html 2012-09-11 14:48:19 +0000
@@ -2,35 +2,27 @@
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">3<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4<head>4<head>
5<title>5<title>{% block title %} {% endblock title %}</title>
6{% block title %}
7{% endblock %}
8</title>
9<script type="text/javascript" src="{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/js/jquery-1.7.2.js"></script>6<script type="text/javascript" src="{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/js/jquery-1.7.2.js"></script>
10<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>7<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>
11<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" />8<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" />
12{% block extrahead %}9{% block extrahead %}
13 {{ form.media }}10{{ form.media }}
14{% endblock %}11{% endblock extrahead %}
15<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>12<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
16{% block style %}13{% block style %}
17{% endblock %}14{% endblock style %}
18
19{% block headcontent %}15{% block headcontent %}
20{% endblock %}16{% endblock headcontent %}
21</head>17</head>
22<body>18<body>
23{% block login %}19{% block login %}{% include "login.html" %}{% endblock login %}
24{% include "login.html" %}20{% block breadcrumbs %} {% endblock breadcrumbs %}
25{% endblock %}21<div class="content">
2622{% block content %}{% endblock content %}
27{% block breadcrumbs %}23</div>
28{% endblock %}24<script type="text/javascript">
2925{% block scripts %} {% endblock scripts %}
30{% block content %}26</script>
31{% endblock %}
32
33{% block scripts %}
34{% endblock %}
35</body>27</body>
36</html>28</html>
3729
=== modified file 'dashboard/frontend/templates/index.html'
--- dashboard/frontend/templates/index.html 2012-09-03 14:12:10 +0000
+++ dashboard/frontend/templates/index.html 2012-09-11 14:48:19 +0000
@@ -66,7 +66,6 @@
66{% endblock %}66{% endblock %}
6767
68{% block scripts %}68{% block scripts %}
69<script type="text/javascript">
70$("#create_button").click(function() {69$("#create_button").click(function() {
71 window.location.href = "{% url IntegrationLoopCreate %}";70 window.location.href = "{% url IntegrationLoopCreate %}";
72});71});
@@ -79,5 +78,4 @@
79$("#create_android_textfield").click(function() {78$("#create_android_textfield").click(function() {
80 window.location.href = "{% url AndroidTextFieldLoopCreate %}";79 window.location.href = "{% url AndroidTextFieldLoopCreate %}";
81});80});
82</script>
83{% endblock %}81{% endblock %}
8482
=== added file 'dashboard/frontend/templates/loop_create.html'
--- dashboard/frontend/templates/loop_create.html 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/templates/loop_create.html 2012-09-11 14:48:19 +0000
@@ -0,0 +1,34 @@
1{% extends "base.html" %}
2{% block breadcrumbs %}
3 <div><a href="{% url Index %}">Index</a></div>
4{% endblock %}
5{% block content %}
6 {% block content_form %}
7 {% comment %}
8 This is where the Django default csrf_token stuff has to go. Each sub-app
9 extending the base classes and templates has to override only this part
10 in the template.
11 {% endcomment %}
12 {% endblock content_form %}
13 {% if form.non_field_errors %}
14 <div class="form_error">
15 {% for err in form.non_field_errors %}
16 <span class="error_message">{{ err }}</span>
17 {% endfor %}
18 </div>
19 {% endif %}
20 {% for field in form %}
21 {% if field.is_hidden %}
22 <div style="display:none;">{{ field }}</div>
23 {% else %}
24 <div class="form_field">
25 {{ field.label_tag }}&#58;&nbsp;{{ field }}
26 {% for err in field.errors %}
27 <span class="error_message">{{ err }}</span>
28 {% endfor %}
29 </div>
30 {% endif %}
31 {% endfor %}
32<div><input type="submit" value="Submit" /></div>
33</form>
34{% endblock content %}
035
=== added file 'dashboard/frontend/templates/loop_detail.html'
--- dashboard/frontend/templates/loop_detail.html 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/templates/loop_detail.html 2012-09-11 14:48:19 +0000
@@ -0,0 +1,32 @@
1{% extends "base.html" %}
2{% block breadcrumbs %}
3 <div><a href="{% url Index %}">Index</a></div>
4{% endblock %}
5{% block content %}
6 <div class="header"><h2>Loop {{ loop_detail.name }}</h2></div>
7 <div class="header"><h4>Details</h4></div>
8 <div>
9 {% for name, value in loop_detail.get_details %}
10 <div>{{ name }}&#58;&nbsp;{{ value }}</div>
11 {% endfor %}
12 </div>
13 <div>
14 <button type="button" id="update_button">Update loop configuration</button>
15 </div>
16 <div class="header"><h4>Builds</h4></div>
17 <div id="builds">
18 {% for build in builds %}
19 {% include "build.html" %}
20 {% endfor %}
21 </div>
22 <div>
23 <button type="button" id="schedule_button">Schedule new build</button>
24 </div>
25{% endblock content %}
26{% block scripts %}
27$("#schedule_button").click(function() {
28 $.get("{% if request.is_secure %}https{% else %}http{% endif %}://{{ request.get_host }}/{{ build_path }}/build/{{ loop_detail.name }}/", function(data) {
29 $(data).prependTo($("#builds"));
30 });
31});
32{% endblock scripts %}
0\ No newline at end of file33\ No newline at end of file
134
=== added file 'dashboard/frontend/templates/textfield_loop_detail.html'
--- dashboard/frontend/templates/textfield_loop_detail.html 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/templates/textfield_loop_detail.html 2012-09-11 14:48:19 +0000
@@ -0,0 +1,28 @@
1{% extends "loop_detail.html" %}
2{% block content %}
3 <div class="header"><h2>Loop {{ loop_detail.name }}</h2></div>
4 <div class="header"><h4>Details</h4></div>
5 <div>
6 {% for name, value in loop_detail.get_details %}
7 <div>{{ name }}&#58;&nbsp;{{ value }}</div>
8 {% endfor %}
9 </div>
10 <div class="header"><h4>Values</h4></div>
11 <div class="textfield">
12 {% for value in loop_detail.get_values %}
13 <div>{{ value }}</div>
14 {% endfor %}
15 </div>
16 <div>
17 <button type="button" id="update_button">Update loop configuration</button>
18 </div>
19 <div class="header"><h4>Builds</h4></div>
20 <div id="builds">
21 {% for build in builds %}
22 {% include "build.html" %}
23 {% endfor %}
24 </div>
25 <div>
26 <button type="button" id="schedule_button">Schedule new build</button>
27 </div>
28{% endblock content %}
029
=== modified file 'dashboard/frontend/templatetags/dashboard_extras.py'
--- dashboard/frontend/templatetags/dashboard_extras.py 2012-08-08 09:21:46 +0000
+++ dashboard/frontend/templatetags/dashboard_extras.py 2012-09-11 14:48:19 +0000
@@ -1,10 +1,7 @@
1from django import template1from django import template
2from django.template.defaultfilters import stringfilter
3
42
5register = template.Library()3register = template.Library()
64
7@register.filter5@register.filter
8def verbose_name(value, field):6def verbose_name(value, field):
9 return value._meta.get_field(field).verbose_name7 return value._meta.get_field(field).verbose_name
10
118
=== modified file 'dashboard/frontend/tests/test_clientresponse.py'
--- dashboard/frontend/tests/test_clientresponse.py 2012-09-04 11:23:32 +0000
+++ dashboard/frontend/tests/test_clientresponse.py 2012-09-11 14:48:19 +0000
@@ -82,6 +82,7 @@
82 template_names = [template.name for template in82 template_names = [template.name for template in
83 response.templates]83 response.templates]
84 self.assertEquals(template_names, ["integration_loop_create.html",84 self.assertEquals(template_names, ["integration_loop_create.html",
85 "loop_create.html",
85 "base.html",86 "base.html",
86 "login.html",87 "login.html",
87 ])88 ])
@@ -108,6 +109,7 @@
108 template_names = [template.name for template in109 template_names = [template.name for template in
109 response.templates]110 response.templates]
110 self.assertEquals(template_names, ["integration_loop_detail.html",111 self.assertEquals(template_names, ["integration_loop_detail.html",
112 "loop_detail.html",
111 "base.html",113 "base.html",
112 "login.html",114 "login.html",
113 ])115 ])
114116
=== modified file 'dashboard/frontend/tests/test_models.py'
--- dashboard/frontend/tests/test_models.py 2012-09-10 12:25:33 +0000
+++ dashboard/frontend/tests/test_models.py 2012-09-11 14:48:19 +0000
@@ -93,6 +93,66 @@
93 self.assertEqual(expected_out,93 self.assertEqual(expected_out,
94 self.loop.dict_for_xml(exclude=exclude_list))94 self.loop.dict_for_xml(exclude=exclude_list))
9595
96 def test_wanted_fields_one_field_included(self):
97 include = ['type']
98 expected_out = [('type', Loop.__class__.__name__)]
99 self.assertEqual(expected_out,
100 self.loop._get_wanted_fields(include=include))
101
102 def test_wanted_fields_two_fields_included(self):
103 include = ['type', 'is_restricted']
104 expected_out = [
105 ('type', Loop.__class__.__name__),
106 ('is_restricted', False)
107 ]
108 self.assertEqual(expected_out,
109 self.loop._get_wanted_fields(include=include))
110
111 def test_wanted_fields_one_field_excluded(self):
112 exclude = ['type']
113 expected_out = [
114 ('id', self.loop.id),
115 ('name', 'a-loop'),
116 ('server', None),
117 ('is_restricted', False),
118 ('is_official', False),
119 ('next_loop', None)
120 ]
121 self.assertEqual(expected_out,
122 self.loop._get_wanted_fields(exclude=exclude))
123
124 def test_wanted_fields_two_fields_excluded(self):
125 exclude = ['type', 'id']
126 expected_out = [
127 ('name', 'a-loop'),
128 ('server', None),
129 ('is_restricted', False),
130 ('is_official', False),
131 ('next_loop', None)
132 ]
133 self.assertEqual(expected_out,
134 self.loop._get_wanted_fields(exclude=exclude))
135
136 def test_wanted_fields_include_and_exclude(self):
137 exclude = ['type']
138 include = ['type', 'id']
139 expected_out = [
140 ('id', self.loop.id)
141 ]
142 self.assertEqual(expected_out,
143 self.loop._get_wanted_fields(include=include,
144 exclude=exclude))
145
146 def test_convert_to_base64(self):
147 from base64 import b64encode
148 exclude = ['type', 'id']
149 fields = self.loop._get_wanted_fields(exclude=exclude, upper=True)
150 encoded_string = u''
151 for field in fields:
152 encoded_string += u'%s=%s\n' % (field[0], str(field[1]))
153 self.assertEqual(b64encode(encoded_string),
154 self.loop.base64_config(exclude=exclude, upper=True))
155
96 def test_continue_down_the_chain_no_next_loop(self):156 def test_continue_down_the_chain_no_next_loop(self):
97 self.loop.continue_down_the_chain()157 self.loop.continue_down_the_chain()
98 self.assertEqual(0, len(self.integration_loop.loopbuild_set.all()))158 self.assertEqual(0, len(self.integration_loop.loopbuild_set.all()))
99159
=== modified file 'dashboard/frontend/urls.py'
--- dashboard/frontend/urls.py 2012-08-31 15:21:11 +0000
+++ dashboard/frontend/urls.py 2012-09-11 14:48:19 +0000
@@ -31,5 +31,4 @@
31 LoopBuildView.as_view(), name='LoopBuild'),31 LoopBuildView.as_view(), name='LoopBuild'),
32 url(r'^loop/get_chainable/$',32 url(r'^loop/get_chainable/$',
33 LoopGetChainableView.as_view(), name='LoopGetChainable'),33 LoopGetChainableView.as_view(), name='LoopGetChainable'),
34
35)34)
3635
=== modified file 'dashboard/frontend/views/loop_build_view.py'
--- dashboard/frontend/views/loop_build_view.py 2012-08-27 16:00:38 +0000
+++ dashboard/frontend/views/loop_build_view.py 2012-09-11 14:48:19 +0000
@@ -26,8 +26,10 @@
2626
27class LoopBuildView(DetailView):27class LoopBuildView(DetailView):
2828
29 # This field should *never* be overridden.
30 slug_field = "name"
31 # The following field has to be overridden in each sub-class.
29 model = Loop32 model = Loop
30 slug_field = "name"
3133
32 @method_decorator(login_required)34 @method_decorator(login_required)
33 def dispatch(self, *args, **kwargs):35 def dispatch(self, *args, **kwargs):
3436
=== added file 'dashboard/frontend/views/loop_create_view.py'
--- dashboard/frontend/views/loop_create_view.py 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/views/loop_create_view.py 2012-09-11 14:48:19 +0000
@@ -0,0 +1,52 @@
1# Copyright (C) 2012 Linaro
2#
3# This file is part of linaro-ci-dashboard.
4#
5# linaro-ci-dashboard is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# linaro-ci-dashboard is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
17
18from django.contrib.auth.decorators import login_required
19from django.core.urlresolvers import reverse
20from django.views.generic.edit import CreateView
21from django.utils.decorators import method_decorator
22from frontend.models.loop import Loop
23from frontend.forms.loop_form import LoopForm
24
25
26class LoopCreateView(CreateView):
27
28 # All these fields have to be overridden in each sub-class.
29 template_name = 'loop_create.html'
30 form_class = LoopForm
31 model = Loop
32 reverse_url = 'LoopDetail'
33
34 def form_valid(self, form):
35 """This method is called when valid form data has been POSTed.
36
37 It should return an HttpResponse."""
38 return super(LoopCreateView, self).form_valid(form)
39
40 @method_decorator(login_required)
41 def dispatch(self, *args, **kwargs):
42 return super(LoopCreateView, self).dispatch(*args, **kwargs)
43
44 def get_context_data(self, **kwargs):
45 """Get the context data passed to template."""
46 context = super(LoopCreateView, self).get_context_data(**kwargs)
47 context['request'] = self.request
48 return context
49
50 def get_success_url(self):
51 """Return the URL when the form is valid."""
52 return reverse(self.reverse_url, args=[self.object.name])
053
=== added file 'dashboard/frontend/views/loop_detail_view.py'
--- dashboard/frontend/views/loop_detail_view.py 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/views/loop_detail_view.py 2012-09-11 14:48:19 +0000
@@ -0,0 +1,46 @@
1# Copyright (C) 2012 Linaro
2#
3# This file is part of linaro-ci-dashboard.
4#
5# linaro-ci-dashboard is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# linaro-ci-dashboard is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
17
18from django.contrib.auth.decorators import login_required
19from django.views.generic.detail import DetailView
20from django.utils.decorators import method_decorator
21from frontend.models.loop import Loop
22
23class LoopDetailView(DetailView):
24
25 # These two fields should *never* be overridden.
26 context_object_name = 'loop_detail'
27 slug_field = 'name'
28 # The following fields have to be overridden in each sub-class.
29 model = Loop
30 template_name = 'loop_detail.html'
31 # This is necessary for each loop to define the URL path for the build
32 # view. This must be overridden.
33 build_path = 'loop'
34
35 @method_decorator(login_required)
36 def dispatch(self, *args, **kwargs):
37 return super(LoopDetailView, self).dispatch(*args, **kwargs)
38
39 def get_context_data(self, **kwargs):
40 '''Get the context data passed to template.'''
41 context = super(LoopDetailView, self).get_context_data(**kwargs)
42 loop_pointer = self.object.loop_ptr
43 context['request'] = self.request
44 context['builds'] = loop_pointer.loopbuild_set.all()
45 context['build_path'] = self.build_path
46 return context
047
=== modified file 'dashboard/frontend/views/loop_get_chainable_view.py'
--- dashboard/frontend/views/loop_get_chainable_view.py 2012-09-04 11:56:22 +0000
+++ dashboard/frontend/views/loop_get_chainable_view.py 2012-09-11 14:48:19 +0000
@@ -25,27 +25,27 @@
25from frontend.models.loop import Loop25from frontend.models.loop import Loop
26from lib.model_getter import ModelGetter26from lib.model_getter import ModelGetter
2727
28
28class LoopGetChainableView(ListView):29class LoopGetChainableView(ListView):
2930
30 model = Loop31 model = Loop
32 template = "list_chainable.html"
3133
32 @method_decorator(login_required)34 @method_decorator(login_required)
33 def dispatch(self, *args, **kwargs):35 def dispatch(self, *args, **kwargs):
34 return super(LoopGetChainableView, self).dispatch(*args, **kwargs)36 return super(LoopGetChainableView, self).dispatch(*args, **kwargs)
3537
36 def render_to_response(self, context):38 def render_to_response(self, context):
37
38 if self.request.is_ajax():39 if self.request.is_ajax():
39
40 request_data = self.request.GET40 request_data = self.request.GET
4141
42 type = request_data.get('type')42 type = request_data.get('type')
43 page = request_data.get('page')
44
43 loop_cls = ModelGetter.get_model(type)45 loop_cls = ModelGetter.get_model(type)
44 chainable_loops_all = loop_cls.get_all_chainable()46 chainable_loops_all = loop_cls.get_all_chainable()
45
46 paginator = Paginator(chainable_loops_all, 10)47 paginator = Paginator(chainable_loops_all, 10)
4748
48 page = request_data.get('page')
49 try:49 try:
50 chainable_loops = paginator.page(page)50 chainable_loops = paginator.page(page)
51 except PageNotAnInteger:51 except PageNotAnInteger:
@@ -57,10 +57,8 @@
57 "chainable_loops": chainable_loops,57 "chainable_loops": chainable_loops,
58 }58 }
5959
60 template = "list_chainable.html"60 return render_to_response(self.template, data,
61 return render_to_response(template, data,61 context_instance=RequestContext(
62 context_instance=RequestContext(62 self.request))
63 self.request))
64
65 else:63 else:
66 raise NotImplementedError64 raise NotImplementedError
6765
=== added file 'dashboard/frontend/views/loop_update_view.py'
--- dashboard/frontend/views/loop_update_view.py 1970-01-01 00:00:00 +0000
+++ dashboard/frontend/views/loop_update_view.py 2012-09-11 14:48:19 +0000
@@ -0,0 +1,55 @@
1# Copyright (C) 2012 Linaro
2#
3# This file is part of linaro-ci-dashboard.
4#
5# linaro-ci-dashboard is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# linaro-ci-dashboard is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
17
18from django.contrib.auth.decorators import login_required
19from django.core.urlresolvers import reverse
20from django.views.generic.edit import UpdateView
21from django.utils.decorators import method_decorator
22from frontend.models.loop import Loop
23from frontend.forms.loop_form import LoopForm
24
25
26class LoopUpdateView(UpdateView):
27
28 # These two fields should *never* be overridden.
29 context_object_name = 'loop_update'
30 slug_field = 'name'
31 # The following fields have to be overridden in each sub-class.
32 template_name = 'loop_create.html'
33 form_class = LoopForm
34 model = Loop
35 reverse_url = 'LoopDetail'
36
37 def form_valid(self, form):
38 """This method is called when valid form data has been POSTed.
39
40 It should return an HttpResponse."""
41 return super(LoopUpdateView, self).form_valid(form)
42
43 @method_decorator(login_required)
44 def dispatch(self, *args, **kwargs):
45 return super(LoopUpdateView, self).dispatch(*args, **kwargs)
46
47 def get_context_data(self, **kwargs):
48 """Get the context data passed to template."""
49 context = super(LoopUpdateView, self).get_context_data(**kwargs)
50 context['request'] = self.request
51 return context
52
53 def get_success_url(self):
54 """Return the URL when the form is valid."""
55 return reverse(self.reverse_url, args=[self.object.name])
056
=== modified file 'dashboard/frontend/widgets/chain_loop_widget.py'
--- dashboard/frontend/widgets/chain_loop_widget.py 2012-09-04 11:23:32 +0000
+++ dashboard/frontend/widgets/chain_loop_widget.py 2012-09-11 14:48:19 +0000
@@ -18,8 +18,6 @@
1818
19from django.forms.widgets import HiddenInput19from django.forms.widgets import HiddenInput
20from django.forms.widgets import MultiWidget20from django.forms.widgets import MultiWidget
21from django.template import loader
22from django.template.base import Context
23from frontend.models.loop import Loop21from frontend.models.loop import Loop
24from frontend.widgets.link_widget import LinkWidget22from frontend.widgets.link_widget import LinkWidget
2523
@@ -34,14 +32,14 @@
34 HiddenInput(attrs=attrs),32 HiddenInput(attrs=attrs),
35 LinkWidget(attrs=attrs),33 LinkWidget(attrs=attrs),
36 )34 )
37
38 super(ChainLoopWidget, self).__init__(_widgets, attrs)35 super(ChainLoopWidget, self).__init__(_widgets, attrs)
3936
40 def decompress(self, value):37 def decompress(self, value):
38 rtn_list = [None, None]
41 if value:39 if value:
42 name = Loop.objects.get(id=value).name40 name = Loop.objects.get(id=value).name
43 return [value, name]41 rtn_list = [value, name]
44 return [None, None]42 return rtn_list
4543
46 def format_output(self, rendered_widgets):44 def format_output(self, rendered_widgets):
47 return u''.join(rendered_widgets)45 return u''.join(rendered_widgets)
4846
=== modified file 'dashboard/jenkinsserver/models/jenkins_server.py'
--- dashboard/jenkinsserver/models/jenkins_server.py 2012-08-24 13:00:02 +0000
+++ dashboard/jenkinsserver/models/jenkins_server.py 2012-09-11 14:48:19 +0000
@@ -102,7 +102,6 @@
102 "Prepare the config.xml to be used for the job."102 "Prepare the config.xml to be used for the job."
103 # Get generic serialization data.103 # Get generic serialization data.
104 loop_params = loop.json()104 loop_params = loop.json()
105
106 template_name = loop.__class__.__name__105 template_name = loop.__class__.__name__
107106
108 # We need to handle the CONFIG parameter as a base64 encoded string.107 # We need to handle the CONFIG parameter as a base64 encoded string.

Subscribers

People subscribed via source and target branches