Merge lp:~dooferlad/linaro-ci-dashboard/add_api_tastypie into lp:linaro-ci-dashboard
- add_api_tastypie
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 69 |
Proposed branch: | lp:~dooferlad/linaro-ci-dashboard/add_api_tastypie |
Merge into: | lp:linaro-ci-dashboard |
Diff against target: |
410 lines (+292/-3) 11 files modified
README (+25/-0) dashboard/frontend/api/api.py (+56/-0) dashboard/frontend/api/urls.py (+13/-0) dashboard/frontend/templates/api_key.html (+6/-0) dashboard/frontend/tests/__init__.py (+23/-0) dashboard/frontend/tests/test_api.py (+95/-0) dashboard/frontend/urls.py (+2/-0) dashboard/frontend/views/api_key.py (+47/-0) dashboard/settings.py (+9/-3) dashboard/urls.py (+1/-0) requirements.txt (+15/-0) |
To merge this branch: | bzr merge lp:~dooferlad/linaro-ci-dashboard/add_api_tastypie |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Milo Casagrande (community) | Approve | ||
Stevan Radaković | Approve | ||
Review via email: mp+146101@code.launchpad.net |
Commit message
Description of the change
Adds a framework for a simple RESTful API using Tastypie (http://
The current API is very limited. The intent of this MP is to get some feedback on if this is a sensible way to implement the API. Future merges will fill it out.
If you are running Ubuntu 12.04 you can run the application, just not unit test the API because it relies on LiveServerTestCase, which was introduced with Django 1.4. To work around this, instructions are provided for setting up an isolated Python environment using virtualenvwrapper are included in the README.
- 77. By James Tunnicliffe
-
Added / updated copyright notices.
Stevan Radaković (stevanr) wrote : | # |
Milo Casagrande (milo) wrote : | # |
Hello James!
Nice work here! :-)
On Fri, Feb 1, 2013 at 12:57 PM, James Tunnicliffe
<email address hidden> wrote:
>
> The current API is very limited. The intent of this MP is to get some feedback on if this is a sensible way to implement the API. Future merges will fill it out.
So, some feedback.
Keep in mind that I never created RESTful API with Python nor Django
(only worked with Java on this front).
Overall, it looks a good Django-nic way of creating RESTful API
without decorators. I hove no idea of the other frameworks around, but
docs for this are well written and seems also up-to-date (a big plus I
would say).
> If you are running Ubuntu 12.04 you can run the application, just not unit test the API because it relies on LiveServerTestCase, which was introduced with Django 1.4. To work around this, instructions are provided for setting up an isolated Python environment using virtualenvwrapper are included in the README.
Regarding this, since we will deploy or install this on a 12.04
server, how are we going to handle/maintain it?
Using virtualenv? Backport? Default backport channel for 12.04 does
not have any newest version of Django available... That is something
we need to keep in mind, because I think we will be using LTS versions
(and next one will be out in 1 year time).
Anyway, using virtualenvwrapper everything worked perfectly, and tests run fine.
> === added file 'dashboard/
> --- dashboard/
> +++ dashboard/
> @@ -0,0 +1,13 @@
> +from django.
> +from dashboard.
> +from tastypie.api import Api
> +
> +v1_api = Api(api_name='v1')
> +
> +# Register new resources here (probably defined in api.py)
> +v1_api.
> +v1_api.
> +
> +urlpatterns = patterns('',
> + (r'^api/', include(
> +)
I have to say I'm more in favor of using a normal numbering scheme
(like "1.0") for API versions (at least for stable ones, maybe a "dev"
one for bleeding-edge stuff).
I guess that if we need to change API version and bump the number, it
would be as easy as this piece of code, as long as Djano does not
complain for the double entry with the same search-pattern (r'^api/').
Did you try if it works? Can't remember out of my mind now...
Ciao!
--
Milo Casagrande | Infrastructure Team
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs
James Tunnicliffe (dooferlad) wrote : | # |
On 4 February 2013 16:09, Stevan Radaković <email address hidden> wrote:
> Hey James,
>
> Nice work rly.. I've managed to run everything pretty much smooth, tests worked, and the code looks good as well, disregarding the problem with django version for unit testing, this is really unfortunate dependency :/
>
> First thing that kinda bothers me is that we require 'v1/' in API links.
> I read the tastypie docs a bit but couldn't get quick answer if this is
> easy to remove or not.. If there's any special reason to keep it tho,
> I'd like to hear it.
I haven't looked into that much, but versioning is probably a good
idea. so incompatible changes can be rolled out without breaking old
clients (well, we will phase out old clients eventually). I don't care
what the version string is though. I like Milo's comment about just
having a number or "dev". My vote is for major.minor version as stable
API versions and dev, which will always point to the latest API.
> Second thing is just a minor code block in tests that stung me in the eye, but nothing special:
>
> 280 + # One loop is of type AndroidLoop, the other ModelBase. Find which is
> 281 + # which.
> 282 + if json[0]['type'] == 'AndroidLoop':
> 283 + android_loop_index = 0
> 284 + base_loop_index = 1
> 285 + else:
> 286 + android_loop_index = 1
> 287 + base_loop_index = 0
> 288 +
> 289 + # Now test the contents of the loop data
> 290 + self.assertEqua
> 291 + self.android_
> 292 + self.assertEqua
> 293 + self.loop.name)
>
> Why not use sets here, like
>
> api_test_names = {json[0]['name'], json[1]['name']}
> test_names = {self.android_
> self.assertEqua
I like your version. I clearly ran out of Python at that point :-)
Thanks,
--
James Tunnicliffe
James Tunnicliffe (dooferlad) wrote : | # |
On 4 February 2013 16:31, Milo Casagrande <email address hidden> wrote:
> Hello James!
>
> Nice work here! :-)
>
> On Fri, Feb 1, 2013 at 12:57 PM, James Tunnicliffe
> <email address hidden> wrote:
>>
>> The current API is very limited. The intent of this MP is to get some feedback on if this is a sensible way to implement the API. Future merges will fill it out.
>
> So, some feedback.
> Keep in mind that I never created RESTful API with Python nor Django
> (only worked with Java on this front).
>
> Overall, it looks a good Django-nic way of creating RESTful API
> without decorators. I hove no idea of the other frameworks around, but
> docs for this are well written and seems also up-to-date (a big plus I
> would say).
>
>> If you are running Ubuntu 12.04 you can run the application, just not unit test the API because it relies on LiveServerTestCase, which was introduced with Django 1.4. To work around this, instructions are provided for setting up an isolated Python environment using virtualenvwrapper are included in the README.
>
> Regarding this, since we will deploy or install this on a 12.04
> server, how are we going to handle/maintain it?
> Using virtualenv? Backport? Default backport channel for 12.04 does
> not have any newest version of Django available... That is something
> we need to keep in mind, because I think we will be using LTS versions
> (and next one will be out in 1 year time).
It is only the testing that requires Django 1.4, not running the API
library. Using LiveServerTestCase instead of TestCase starts up a
server per-test that the test can interact with. This was introducted
with 1.4. It shouldn't change the functionality of the API at all. Of
course, there is a risk that a bug will be present running under 1.3
that we don't see with 1.4, but so far I haven't encountered any.
> Anyway, using virtualenvwrapper everything worked perfectly, and tests run fine.
>
>> === added file 'dashboard/
>> --- dashboard/
>> +++ dashboard/
>> @@ -0,0 +1,13 @@
>> +from django.
>> +from dashboard.
>> +from tastypie.api import Api
>> +
>> +v1_api = Api(api_name='v1')
>> +
>> +# Register new resources here (probably defined in api.py)
>> +v1_api.
>> +v1_api.
>> +
>> +urlpatterns = patterns('',
>> + (r'^api/', include(
>> +)
>
> I have to say I'm more in favor of using a normal numbering scheme
> (like "1.0") for API versions (at least for stable ones, maybe a "dev"
> one for bleeding-edge stuff).
> I guess that if we need to change API version and bump the number, it
> would be as easy as this piece of code, as long as Djano does not
> complain for the double entry with the same search-pattern (r'^api/').
> Did you try if it works? Can't remember out of my mind now...
I haven't tried it. I am sure we can find something that works and we
all like. For now I will change the api_name to "dev" and when we
release we can create a duplicate interface under 1.0, v1.0 or
whatev...
- 78. By James Tunnicliffe
-
Changes as suggested from merge proposal:
* Name API version "dev" for the moment
* Tidy up tests
Stevan Radaković (stevanr) wrote : | # |
Thanks for the changes!
Approve +1
Milo Casagrande (milo) wrote : | # |
> I haven't looked into that much, but versioning is probably a good
> idea. so incompatible changes can be rolled out without breaking old
> clients (well, we will phase out old clients eventually). I don't care
> what the version string is though. I like Milo's comment about just
> having a number or "dev". My vote is for major.minor version as stable
> API versions and dev, which will always point to the latest API.
I'm in favor of introducing some sort of versioning too: major.minor numbering and a "dev" one looks OK.
Anyway, MP looks good.
Preview Diff
1 | === modified file 'README' | |||
2 | --- README 2012-09-21 02:10:21 +0000 | |||
3 | +++ README 2013-02-04 17:57:22 +0000 | |||
4 | @@ -16,6 +16,31 @@ | |||
5 | 16 | python-dev | 16 | python-dev |
6 | 17 | build-essential | 17 | build-essential |
7 | 18 | openjdk-6-jre-headless | 18 | openjdk-6-jre-headless |
8 | 19 | python-tastypie | ||
9 | 20 | |||
10 | 21 | To unit test the API that tastypie provides you need Django version of at least | ||
11 | 22 | 1.4. If you have Django 1.3 the API will still work, but not be unit tested. | ||
12 | 23 | |||
13 | 24 | If you are running Ubuntu 12.04 or earlier, you can install Django 1.4 in | ||
14 | 25 | an isolated Python environment using http://www.virtualenv.org/en/latest/. | ||
15 | 26 | These instructions are for the slightly friendlier interface offered by | ||
16 | 27 | http://virtualenvwrapper.readthedocs.org/en/latest/. | ||
17 | 28 | |||
18 | 29 | First install virtualenv and virtualenv wrapper: | ||
19 | 30 | > pip install virtualenvwrapper | ||
20 | 31 | |||
21 | 32 | Create a virtual environment to work in: | ||
22 | 33 | > mkvirtualenv ciloops | ||
23 | 34 | |||
24 | 35 | Enable the virtual environment: | ||
25 | 36 | > workon ciloops | ||
26 | 37 | |||
27 | 38 | Install the required Python packages: | ||
28 | 39 | > pip install -r requirements.txt | ||
29 | 40 | |||
30 | 41 | If any new Python packages are required, they can be installed using pip then | ||
31 | 42 | a new requirements.txt generated: | ||
32 | 43 | > pip freeze > requirements.txt | ||
33 | 19 | 44 | ||
34 | 20 | Running the application | 45 | Running the application |
35 | 21 | ----------------------- | 46 | ----------------------- |
36 | 22 | 47 | ||
37 | === added directory 'dashboard/frontend/api' | |||
38 | === added file 'dashboard/frontend/api/__init__.py' | |||
39 | === added file 'dashboard/frontend/api/api.py' | |||
40 | --- dashboard/frontend/api/api.py 1970-01-01 00:00:00 +0000 | |||
41 | +++ dashboard/frontend/api/api.py 2013-02-04 17:57:22 +0000 | |||
42 | @@ -0,0 +1,56 @@ | |||
43 | 1 | # Copyright (C) 2013 Linaro | ||
44 | 2 | # | ||
45 | 3 | # This file is part of linaro-ci-dashboard. | ||
46 | 4 | # | ||
47 | 5 | # linaro-ci-dashboard is free software: you can redistribute it and/or modify | ||
48 | 6 | # it under the terms of the GNU Affero General Public License as published by | ||
49 | 7 | # the Free Software Foundation, either version 3 of the License, or | ||
50 | 8 | # (at your option) any later version. | ||
51 | 9 | # | ||
52 | 10 | # linaro-ci-dashboard is distributed in the hope that it will be useful, | ||
53 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
54 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
55 | 13 | # GNU Affero General Public License for more details. | ||
56 | 14 | # | ||
57 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
58 | 16 | # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. | ||
59 | 17 | |||
60 | 18 | from tastypie.resources import ModelResource | ||
61 | 19 | from tastypie.authorization import DjangoAuthorization | ||
62 | 20 | from tastypie.authentication import ApiKeyAuthentication | ||
63 | 21 | from dashboard.frontend.models.loop import Loop | ||
64 | 22 | |||
65 | 23 | """ | ||
66 | 24 | Add API endpoint logic here. The documentation to do this can be found here: | ||
67 | 25 | http://django-tastypie.readthedocs.org/en/latest/tutorial.html | ||
68 | 26 | |||
69 | 27 | After creating a class here, make sure you register it in urls.py | ||
70 | 28 | |||
71 | 29 | TODO: Handle loop creation and builds. | ||
72 | 30 | """ | ||
73 | 31 | |||
74 | 32 | |||
75 | 33 | class LoginTestResource(ModelResource): | ||
76 | 34 | class Meta: | ||
77 | 35 | """This exists just so a client can test its login data. | ||
78 | 36 | |||
79 | 37 | It must use the same authentication mechanism as other protected | ||
80 | 38 | classes so it will reject connections that aren't authenticated. | ||
81 | 39 | |||
82 | 40 | The return data doesn't matter. Ideally, it should put minimal load | ||
83 | 41 | on the server to generate the return data. | ||
84 | 42 | """ | ||
85 | 43 | |||
86 | 44 | queryset = Loop.objects.none() | ||
87 | 45 | resource_name = 'login_test' | ||
88 | 46 | authorization = DjangoAuthorization() | ||
89 | 47 | authentication = ApiKeyAuthentication() | ||
90 | 48 | |||
91 | 49 | |||
92 | 50 | class LoopsResource(ModelResource): | ||
93 | 51 | class Meta: | ||
94 | 52 | """Returns a list of all loops.""" | ||
95 | 53 | queryset = Loop.objects.all() | ||
96 | 54 | resource_name = 'loops' | ||
97 | 55 | authorization = DjangoAuthorization() | ||
98 | 56 | authentication = ApiKeyAuthentication() | ||
99 | 0 | 57 | ||
100 | === added file 'dashboard/frontend/api/urls.py' | |||
101 | --- dashboard/frontend/api/urls.py 1970-01-01 00:00:00 +0000 | |||
102 | +++ dashboard/frontend/api/urls.py 2013-02-04 17:57:22 +0000 | |||
103 | @@ -0,0 +1,13 @@ | |||
104 | 1 | from django.conf.urls.defaults import patterns, include | ||
105 | 2 | from dashboard.frontend.api.api import * | ||
106 | 3 | from tastypie.api import Api | ||
107 | 4 | |||
108 | 5 | v1_api = Api(api_name='dev') | ||
109 | 6 | |||
110 | 7 | # Register new resources here (probably defined in api.py) | ||
111 | 8 | v1_api.register(LoginTestResource()) | ||
112 | 9 | v1_api.register(LoopsResource()) | ||
113 | 10 | |||
114 | 11 | urlpatterns = patterns('', | ||
115 | 12 | (r'^api/', include(v1_api.urls)), | ||
116 | 13 | ) | ||
117 | 0 | 14 | ||
118 | === added file 'dashboard/frontend/templates/api_key.html' | |||
119 | --- dashboard/frontend/templates/api_key.html 1970-01-01 00:00:00 +0000 | |||
120 | +++ dashboard/frontend/templates/api_key.html 2013-02-04 17:57:22 +0000 | |||
121 | @@ -0,0 +1,6 @@ | |||
122 | 1 | {% if request.user.username %} | ||
123 | 2 | <div>Your Linaro CI CLI login information is:</div> | ||
124 | 3 | <div class="data">User: {{ request.user }}</div> | ||
125 | 4 | <div class="data">API Key: {{ api_key }}</div> | ||
126 | 5 | {% else %} | ||
127 | 6 | {% endif %} | ||
128 | 0 | 7 | ||
129 | === modified file 'dashboard/frontend/tests/__init__.py' | |||
130 | --- dashboard/frontend/tests/__init__.py 2012-09-14 08:15:50 +0000 | |||
131 | +++ dashboard/frontend/tests/__init__.py 2013-02-04 17:57:22 +0000 | |||
132 | @@ -1,9 +1,29 @@ | |||
133 | 1 | # Copyright (C) 2013 Linaro | ||
134 | 2 | # | ||
135 | 3 | # This file is part of linaro-ci-dashboard. | ||
136 | 4 | # | ||
137 | 5 | # linaro-ci-dashboard is free software: you can redistribute it and/or modify | ||
138 | 6 | # it under the terms of the GNU Affero General Public License as published by | ||
139 | 7 | # the Free Software Foundation, either version 3 of the License, or | ||
140 | 8 | # (at your option) any later version. | ||
141 | 9 | # | ||
142 | 10 | # linaro-ci-dashboard is distributed in the hope that it will be useful, | ||
143 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
144 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
145 | 13 | # GNU Affero General Public License for more details. | ||
146 | 14 | # | ||
147 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
148 | 16 | # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. | ||
149 | 17 | |||
150 | 1 | from dashboard.frontend.tests.test_views import * | 18 | from dashboard.frontend.tests.test_views import * |
151 | 2 | from dashboard.frontend.tests.test_models import * | 19 | from dashboard.frontend.tests.test_models import * |
152 | 3 | from dashboard.frontend.tests.test_clientresponse import * | 20 | from dashboard.frontend.tests.test_clientresponse import * |
153 | 4 | from dashboard.frontend.tests.test_custom_commands import * | 21 | from dashboard.frontend.tests.test_custom_commands import * |
154 | 5 | from dashboard.frontend.tests.test_xml_to_dict import * | 22 | from dashboard.frontend.tests.test_xml_to_dict import * |
155 | 6 | 23 | ||
156 | 24 | import django | ||
157 | 25 | if not(django.VERSION[0] == 1 and django.VERSION[1] <= 3): | ||
158 | 26 | from dashboard.frontend.tests.test_api import * | ||
159 | 7 | 27 | ||
160 | 8 | #starts the test suite | 28 | #starts the test suite |
161 | 9 | __test__ = { | 29 | __test__ = { |
162 | @@ -15,3 +35,6 @@ | |||
163 | 15 | 'XmlToDictTest': XmlToDictTest, | 35 | 'XmlToDictTest': XmlToDictTest, |
164 | 16 | 'DictToXmlTest': DictToXmlTest, | 36 | 'DictToXmlTest': DictToXmlTest, |
165 | 17 | } | 37 | } |
166 | 38 | |||
167 | 39 | if not(django.VERSION[0] == 1 and django.VERSION[1] <= 3): | ||
168 | 40 | __test__['ApiTests'] = ApiTests | ||
169 | 18 | 41 | ||
170 | === added file 'dashboard/frontend/tests/test_api.py' | |||
171 | --- dashboard/frontend/tests/test_api.py 1970-01-01 00:00:00 +0000 | |||
172 | +++ dashboard/frontend/tests/test_api.py 2013-02-04 17:57:22 +0000 | |||
173 | @@ -0,0 +1,95 @@ | |||
174 | 1 | # Copyright (C) 2013 Linaro | ||
175 | 2 | # | ||
176 | 3 | # This file is part of linaro-ci-dashboard. | ||
177 | 4 | # | ||
178 | 5 | # linaro-ci-dashboard is free software: you can redistribute it and/or modify | ||
179 | 6 | # it under the terms of the GNU Affero General Public License as published by | ||
180 | 7 | # the Free Software Foundation, either version 3 of the License, or | ||
181 | 8 | # (at your option) any later version. | ||
182 | 9 | # | ||
183 | 10 | # linaro-ci-dashboard is distributed in the hope that it will be useful, | ||
184 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
185 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
186 | 13 | # GNU Affero General Public License for more details. | ||
187 | 14 | # | ||
188 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
189 | 16 | # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. | ||
190 | 17 | |||
191 | 18 | from django.contrib.auth.models import User | ||
192 | 19 | from django.test import LiveServerTestCase | ||
193 | 20 | from tastypie.models import ApiKey | ||
194 | 21 | from dashboard.frontend.models.loop import Loop | ||
195 | 22 | from dashboard.frontend.android_build.models.android_loop import AndroidLoop | ||
196 | 23 | import requests | ||
197 | 24 | from urlparse import urljoin | ||
198 | 25 | |||
199 | 26 | |||
200 | 27 | class ApiTests(LiveServerTestCase): | ||
201 | 28 | # Use ``fixtures`` & ``urls`` as normal. See Django's ``TestCase`` | ||
202 | 29 | # documentation for the gory details. | ||
203 | 30 | fixtures = ['test_entries.json'] | ||
204 | 31 | |||
205 | 32 | def setUp(self): | ||
206 | 33 | super(ApiTests, self).setUp() | ||
207 | 34 | |||
208 | 35 | # Create a user. | ||
209 | 36 | self.username = 'daniel' | ||
210 | 37 | self.password = 'pass' | ||
211 | 38 | self.user = User.objects.create_user( | ||
212 | 39 | self.username, 'daniel@example.com', self.password) | ||
213 | 40 | |||
214 | 41 | self.api_key = "0" | ||
215 | 42 | |||
216 | 43 | # Add a couple of jobs to test against. | ||
217 | 44 | self.android_loop = AndroidLoop() | ||
218 | 45 | self.android_loop.name = "testjob_android" | ||
219 | 46 | self.android_loop.build_type = "build-android" | ||
220 | 47 | self.android_loop.save() | ||
221 | 48 | |||
222 | 49 | self.loop = Loop() | ||
223 | 50 | self.loop.name = 'a-loop' | ||
224 | 51 | self.loop.type = Loop.__class__.__name__ | ||
225 | 52 | self.loop.save() | ||
226 | 53 | |||
227 | 54 | def get(self, url): | ||
228 | 55 | payload = {"username": self.username, | ||
229 | 56 | "api_key": self.api_key, | ||
230 | 57 | "format": "json"} | ||
231 | 58 | url = urljoin("http://localhost:8081", "/api/dev/" + url) | ||
232 | 59 | return requests.get(url, params=payload) | ||
233 | 60 | |||
234 | 61 | def login(self): | ||
235 | 62 | self.api_key = ApiKey.objects.create(user=self.user).key | ||
236 | 63 | #self.create_apikey(self.username, self.api_key) | ||
237 | 64 | |||
238 | 65 | def test_login_test_unauthorzied(self): | ||
239 | 66 | r = self.get("login_test") | ||
240 | 67 | self.assertEqual(r.status_code, requests.codes.unauthorized) | ||
241 | 68 | |||
242 | 69 | def test_loops_unauthorzied(self): | ||
243 | 70 | r = self.get("loops") | ||
244 | 71 | self.assertEqual(r.status_code, requests.codes.unauthorized) | ||
245 | 72 | |||
246 | 73 | def test_login_test_authorized(self): | ||
247 | 74 | self.login() | ||
248 | 75 | r = self.get("login_test") | ||
249 | 76 | self.assertEqual(r.status_code, requests.codes.ok) | ||
250 | 77 | |||
251 | 78 | def test_loops_authorized(self): | ||
252 | 79 | self.login() | ||
253 | 80 | r = self.get("loops") | ||
254 | 81 | self.assertEqual(r.status_code, requests.codes.ok) | ||
255 | 82 | |||
256 | 83 | def test_loops_list(self): | ||
257 | 84 | self.login() | ||
258 | 85 | r = self.get("loops") | ||
259 | 86 | self.assertEqual(r.status_code, requests.codes.ok) | ||
260 | 87 | json = r.json()['objects'] | ||
261 | 88 | |||
262 | 89 | # 2 defined loops. Both should be returned. | ||
263 | 90 | self.assertEqual(len(json), 2) | ||
264 | 91 | |||
265 | 92 | # Make sure both loops contain the expected data. | ||
266 | 93 | api_test_names = {json[0]['name'], json[1]['name']} | ||
267 | 94 | test_names = {self.android_loop.name, self.loop.name} | ||
268 | 95 | self.assertEqual(api_test_names, test_names) | ||
269 | 0 | 96 | ||
270 | === modified file 'dashboard/frontend/urls.py' | |||
271 | --- dashboard/frontend/urls.py 2012-09-24 19:09:05 +0000 | |||
272 | +++ dashboard/frontend/urls.py 2013-02-04 17:57:22 +0000 | |||
273 | @@ -24,6 +24,7 @@ | |||
274 | 24 | from frontend.views.loop_build_view import LoopBuildView | 24 | from frontend.views.loop_build_view import LoopBuildView |
275 | 25 | from frontend.views.loop_get_chainable_view import LoopGetChainableView | 25 | from frontend.views.loop_get_chainable_view import LoopGetChainableView |
276 | 26 | from frontend.views.loop_build_detail_view import LoopBuildDetailView | 26 | from frontend.views.loop_build_detail_view import LoopBuildDetailView |
277 | 27 | from frontend.views.api_key import ApiKeyView | ||
278 | 27 | 28 | ||
279 | 28 | 29 | ||
280 | 29 | urlpatterns = patterns('', | 30 | urlpatterns = patterns('', |
281 | @@ -40,4 +41,5 @@ | |||
282 | 40 | LavaSelectView.as_view(), name='LavaSelectView'), | 41 | LavaSelectView.as_view(), name='LavaSelectView'), |
283 | 41 | url(r'^lava/ajax/get-test-names/$', | 42 | url(r'^lava/ajax/get-test-names/$', |
284 | 42 | LavaSelectTestNames.as_view(), name='LavaSelectTestsNames'), | 43 | LavaSelectTestNames.as_view(), name='LavaSelectTestsNames'), |
285 | 44 | url(r'^api_key/$', ApiKeyView.as_view(), name='ApiKeyView'), | ||
286 | 43 | ) | 45 | ) |
287 | 44 | 46 | ||
288 | === added file 'dashboard/frontend/views/api_key.py' | |||
289 | --- dashboard/frontend/views/api_key.py 1970-01-01 00:00:00 +0000 | |||
290 | +++ dashboard/frontend/views/api_key.py 2013-02-04 17:57:22 +0000 | |||
291 | @@ -0,0 +1,47 @@ | |||
292 | 1 | # Copyright (C) 2013 Linaro | ||
293 | 2 | # | ||
294 | 3 | # This file is part of linaro-ci-dashboard. | ||
295 | 4 | # | ||
296 | 5 | # linaro-ci-dashboard is free software: you can redistribute it and/or modify | ||
297 | 6 | # it under the terms of the GNU Affero General Public License as published by | ||
298 | 7 | # the Free Software Foundation, either version 3 of the License, or | ||
299 | 8 | # (at your option) any later version. | ||
300 | 9 | # | ||
301 | 10 | # linaro-ci-dashboard is distributed in the hope that it will be useful, | ||
302 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
303 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
304 | 13 | # GNU Affero General Public License for more details. | ||
305 | 14 | # | ||
306 | 15 | # You should have received a copy of the GNU Affero General Public License | ||
307 | 16 | # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>. | ||
308 | 17 | |||
309 | 18 | from django.views.generic.base import TemplateView | ||
310 | 19 | from tastypie.models import ApiKey | ||
311 | 20 | from django.contrib.auth.decorators import login_required | ||
312 | 21 | from django.utils.decorators import method_decorator | ||
313 | 22 | |||
314 | 23 | |||
315 | 24 | class ApiKeyView(TemplateView): | ||
316 | 25 | """Provide the user with their API key, generating one if required.""" | ||
317 | 26 | template_name = "api_key.html" | ||
318 | 27 | |||
319 | 28 | @method_decorator(login_required) | ||
320 | 29 | def dispatch(self, *args, **kwargs): | ||
321 | 30 | return super(ApiKeyView, self).dispatch(*args, **kwargs) | ||
322 | 31 | |||
323 | 32 | def get_context_data(self, **kwargs): | ||
324 | 33 | context = super(ApiKeyView, self).get_context_data(**kwargs) | ||
325 | 34 | context['request'] = self.request | ||
326 | 35 | |||
327 | 36 | # TODO: Reset API key on request. This can be done by: | ||
328 | 37 | # api_key.key = None | ||
329 | 38 | # api_key.save() -- will generate a new key (won't accept None) | ||
330 | 39 | |||
331 | 40 | try: | ||
332 | 41 | api_key = ApiKey.objects.get(user=self.request.user) | ||
333 | 42 | except ApiKey.DoesNotExist: | ||
334 | 43 | api_key = ApiKey.objects.create(user=self.request.user) | ||
335 | 44 | |||
336 | 45 | context['api_key'] = api_key.key | ||
337 | 46 | |||
338 | 47 | return context | ||
339 | 0 | 48 | ||
340 | === modified file 'dashboard/settings.py' | |||
341 | --- dashboard/settings.py 2012-10-04 15:39:41 +0000 | |||
342 | +++ dashboard/settings.py 2013-02-04 17:57:22 +0000 | |||
343 | @@ -18,6 +18,7 @@ | |||
344 | 18 | 18 | ||
345 | 19 | import os | 19 | import os |
346 | 20 | import sys | 20 | import sys |
347 | 21 | import django | ||
348 | 21 | 22 | ||
349 | 22 | DEBUG = True | 23 | DEBUG = True |
350 | 23 | TEMPLATE_DEBUG = DEBUG | 24 | TEMPLATE_DEBUG = DEBUG |
351 | @@ -127,11 +128,16 @@ | |||
352 | 127 | 'django.middleware.common.CommonMiddleware', | 128 | 'django.middleware.common.CommonMiddleware', |
353 | 128 | 'django.contrib.sessions.middleware.SessionMiddleware', | 129 | 'django.contrib.sessions.middleware.SessionMiddleware', |
354 | 129 | 'django.middleware.csrf.CsrfViewMiddleware', | 130 | 'django.middleware.csrf.CsrfViewMiddleware', |
355 | 130 | 'django.middleware.csrf.CsrfResponseMiddleware', | ||
356 | 131 | 'django.contrib.auth.middleware.AuthenticationMiddleware', | 131 | 'django.contrib.auth.middleware.AuthenticationMiddleware', |
357 | 132 | 'django.contrib.messages.middleware.MessageMiddleware', | 132 | 'django.contrib.messages.middleware.MessageMiddleware', |
358 | 133 | ) | 133 | ) |
359 | 134 | 134 | ||
360 | 135 | # The CSRF middleware moved in Django 1.4... | ||
361 | 136 | if django.VERSION[0] == 1 and django.VERSION[1] <= 3: | ||
362 | 137 | MIDDLEWARE_CLASSES += ('django.middleware.csrf.CsrfResponseMiddleware',) | ||
363 | 138 | else: | ||
364 | 139 | MIDDLEWARE_CLASSES += ('django.middleware.csrf.CsrfViewMiddleware',) | ||
365 | 140 | |||
366 | 135 | ROOT_URLCONF = 'dashboard.urls' | 141 | ROOT_URLCONF = 'dashboard.urls' |
367 | 136 | 142 | ||
368 | 137 | INSTALLED_APPS = ( | 143 | INSTALLED_APPS = ( |
369 | @@ -151,9 +157,9 @@ | |||
370 | 151 | 'frontend.integration_loop', | 157 | 'frontend.integration_loop', |
371 | 152 | 'frontend.android_textfield_loop', | 158 | 'frontend.android_textfield_loop', |
372 | 153 | 'frontend.hwpack_loop', | 159 | 'frontend.hwpack_loop', |
373 | 160 | 'frontend.api', | ||
374 | 161 | 'tastypie', | ||
375 | 154 | 'south', | 162 | 'south', |
376 | 155 | # Uncomment the next line to enable the admin: | ||
377 | 156 | # 'django.contrib.admin', | ||
378 | 157 | # Uncomment the next line to enable admin documentation: | 163 | # Uncomment the next line to enable admin documentation: |
379 | 158 | # 'django.contrib.admindocs', | 164 | # 'django.contrib.admindocs', |
380 | 159 | ) | 165 | ) |
381 | 160 | 166 | ||
382 | === modified file 'dashboard/urls.py' | |||
383 | --- dashboard/urls.py 2012-09-24 19:09:05 +0000 | |||
384 | +++ dashboard/urls.py 2013-02-04 17:57:22 +0000 | |||
385 | @@ -39,4 +39,5 @@ | |||
386 | 39 | url(r'^', include('dashboard.frontend.integration_loop.urls')), | 39 | url(r'^', include('dashboard.frontend.integration_loop.urls')), |
387 | 40 | url(r'^', include('dashboard.frontend.android_textfield_loop.urls')), | 40 | url(r'^', include('dashboard.frontend.android_textfield_loop.urls')), |
388 | 41 | url(r'^', include('dashboard.frontend.hwpack_loop.urls')), | 41 | url(r'^', include('dashboard.frontend.hwpack_loop.urls')), |
389 | 42 | url(r'^', include('dashboard.frontend.api.urls')), | ||
390 | 42 | ) | 43 | ) |
391 | 43 | 44 | ||
392 | === added file 'requirements.txt' | |||
393 | --- requirements.txt 1970-01-01 00:00:00 +0000 | |||
394 | +++ requirements.txt 2013-02-04 17:57:22 +0000 | |||
395 | @@ -0,0 +1,15 @@ | |||
396 | 1 | Django==1.4.3 | ||
397 | 2 | South==0.7.6 | ||
398 | 3 | argparse==1.2.1 | ||
399 | 4 | distribute==0.6.24 | ||
400 | 5 | django-openid-auth==0.4 | ||
401 | 6 | django-openid-consumer==0.1.1 | ||
402 | 7 | django-tastypie==0.9.11 | ||
403 | 8 | jenkins==1.0.2 | ||
404 | 9 | mimeparse==0.1.3 | ||
405 | 10 | mock==1.0.1 | ||
406 | 11 | python-dateutil==1.5 | ||
407 | 12 | python-jenkins==0.2 | ||
408 | 13 | python-openid==2.2.5 | ||
409 | 14 | requests==1.1.0 | ||
410 | 15 | wsgiref==0.1.2 |
Hey James,
Nice work rly.. I've managed to run everything pretty much smooth, tests worked, and the code looks good as well, disregarding the problem with django version for unit testing, this is really unfortunate dependency :/
First thing that kinda bothers me is that we require 'v1/' in API links. I read the tastypie docs a bit but couldn't get quick answer if this is easy to remove or not.. If there's any special reason to keep it tho, I'd like to hear it.
Second thing is just a minor code block in tests that stung me in the eye, but nothing special:
280 + # One loop is of type AndroidLoop, the other ModelBase. Find which is l(json[ android_ loop_index] ['name' ], loop.name) l(json[ base_loop_ index][ 'name'] ,
281 + # which.
282 + if json[0]['type'] == 'AndroidLoop':
283 + android_loop_index = 0
284 + base_loop_index = 1
285 + else:
286 + android_loop_index = 1
287 + base_loop_index = 0
288 +
289 + # Now test the contents of the loop data
290 + self.assertEqua
291 + self.android_
292 + self.assertEqua
293 + self.loop.name)
Why not use sets here, like
api_test_names = {json[0]['name'], json[1]['name']} loop.name, self.loop.name} l(api_test_ names, test_names)
test_names = {self.android_
self.assertEqua