Merge lp:~elachuni/ubuntu-webcatalog/machine-model into lp:ubuntu-webcatalog
- machine-model
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Anthony Lenton |
Approved revision: | 56 |
Merged at revision: | 46 |
Proposed branch: | lp:~elachuni/ubuntu-webcatalog/machine-model |
Merge into: | lp:ubuntu-webcatalog |
Diff against target: |
1224 lines (+991/-9) 14 files modified
src/webcatalog/admin.py (+7/-0) src/webcatalog/api/forms.py (+46/-0) src/webcatalog/api/handlers.py (+81/-0) src/webcatalog/api/urls.py (+20/-2) src/webcatalog/migrations/0007_add_machine.py (+136/-0) src/webcatalog/migrations/0008_add_oauth_tables.py (+176/-0) src/webcatalog/models/__init__.py (+2/-0) src/webcatalog/models/applications.py (+17/-3) src/webcatalog/models/oauthtoken.py (+3/-3) src/webcatalog/schema.py (+3/-0) src/webcatalog/tests/__init__.py (+6/-0) src/webcatalog/tests/factory.py (+52/-0) src/webcatalog/tests/test_api.py (+254/-0) src/webcatalog/tests/test_handlers.py (+188/-1) |
To merge this branch: | bzr merge lp:~elachuni/ubuntu-webcatalog/machine-model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ricardo Kirkner (community) | Approve | ||
Review via email: mp+69506@code.launchpad.net |
Commit message
Added a Machine model and fleshes out the api to interact with it.
Description of the change
Overview
========
This branch adds a Machine model and fleshes out the api to interact with the model.
Details
=======
Work was based on a stub api provided by didrocks, that's been merged into this branch already.
Code has been tested using the oneconf client, besides the handler tests included.
The model itself is just a boilerplate bag of fields, so no tests added for that. Client tests (using the piston-mini-client code oneconf uses) can be included in a later branch.
There's no auth protection on the handlers yet, so all machine data gets piled onto the first user in the system. This needs to be added for the api to be really usable, but can also be included in a later branch.
- 50. By Anthony Lenton
-
Two small code style fixes
- 51. By Anthony Lenton
-
Merged in latest changes from trunk
- 52. By Anthony Lenton
-
Plugged in authentication.
- 53. By Anthony Lenton
-
Plugged in api tests.
- 54. By Anthony Lenton
-
Completed tests for list-machines.
- 55. By Anthony Lenton
-
Completed API tests.
Anthony Lenton (elachuni) wrote : | # |
Hi Ricardo,
Thanks for the review! I'm attaching a diff that fixes all your
comments.
On 07/29/2011 11:31 AM, Ricardo Kirkner wrote:
> l. 115: still using hardcoded User instead of the one from the request
True! Fixed
> l. 736: factory methods default prefix don't end in '-' (let's be consistent)
Yep, fixed also.
> l. 845: you don't want to 'import simplejson' directly, but 'from django.utils import simplejson', as that will try to load python's json module first, and fallback to the simplejson module provided by django if not found
Fixed.
> l. 911, why not check for status code here? to make sure it's 204?
Yep, it makes sense to check the status also. The test wants to verify
that the instance was actually deleted from the db, hence the model
check.
> l. 1003 why test for the content instead of the status code ('Success' vs 200)?. Why is the content not the same as with the status ('Success' vs 'ok')?
Fixed the test so that it checks for both status and content (with
assertContains).
The 'Success' and 'ok' responses are for different things (command was
successful, vs. current status of the server). I don't think it makes
much sense to make these two response messages the same.
> l. 1064: why use None for the request when you have a request you can use (self.request)?
And, also fixed.
I'm attaching a diff with just these fixes, hope it works :)
1 | === modified file 'src/webcatalog/api/handlers.py' |
2 | --- src/webcatalog/api/handlers.py 2011-07-28 18:49:26 +0000 |
3 | +++ src/webcatalog/api/handlers.py 2011-07-29 15:06:35 +0000 |
4 | @@ -49,10 +49,7 @@ |
5 | fields = ('uuid', 'hostname', 'logo_checksum', 'packages_checksum') |
6 | |
7 | def read(self, request): |
8 | - # Once the api is authenticated this will be just request.user: |
9 | - user = User.objects.all()[0] |
10 | - |
11 | - result = Machine.objects.filter(owner=user) |
12 | + result = Machine.objects.filter(owner=request.user) |
13 | return result.defer('package_list') |
14 | |
15 | class MachineHandler(BaseHandler): |
16 | |
17 | === modified file 'src/webcatalog/tests/factory.py' |
18 | --- src/webcatalog/tests/factory.py 2011-07-28 20:39:00 +0000 |
19 | +++ src/webcatalog/tests/factory.py 2011-07-29 15:08:51 +0000 |
20 | @@ -150,14 +150,14 @@ |
21 | user = self.make_user() |
22 | |
23 | consumer_key = user.useropenid_set.get().claimed_id.split('/')[-1] |
24 | - consumer_secret = self.get_unique_string(prefix='consumer-secret') |
25 | + consumer_secret = self.get_unique_string(prefix='consumer-secret-') |
26 | consumer = Consumer(user=user, key=consumer_key, |
27 | secret=consumer_secret) |
28 | if save: |
29 | consumer.save() |
30 | - token_string = self.get_unique_string(prefix='token') |
31 | - token_secret = self.get_unique_string(prefix='token-secret') |
32 | - token_name = self.get_unique_string(prefix='token-name') |
33 | + token_string = self.get_unique_string(prefix='token-') |
34 | + token_secret = self.get_unique_string(prefix='token-secret-') |
35 | + token_name = self.get_unique_string(prefix='token-name-') |
36 | token = Token(consumer=consumer, token=token_string, |
37 | token_secret=token_secret, name=token_name) |
38 | if save: |
39 | |
40 | === modified file 'src/webcatalog/tests/test_api.py' |
41 | --- src/webcatalog/tests/test_api.py 2011-07-29 13:08:31 +0000 |
42 | +++ src/webcatalog/tests/test_api.py 2011-07-29 15:12:08 +0000 |
43 | @@ -30,7 +30,7 @@ |
44 | 'UpdatePackageListTestCase', |
45 | ] |
46 | |
47 | -import simplejson |
48 | +from django.utils import simplejson |
49 | |
50 | from django.test import TestCase |
51 | from oauth.oauth import ( |
52 | @@ -144,6 +144,7 @@ |
53 | response = self.client.delete(url, |
54 | **self.auth_header_for_user(url, user=machine.owner)) |
55 | |
56 | + self.assertEqual(204, response.status_code) |
57 | self.assertRaises(Machine.DoesNotExist, Machine.objects.get, |
58 | uuid=machine.uuid, owner=machine.owner) |
59 | |
60 | @@ -237,7 +238,7 @@ |
61 | content_type='application/json', |
62 | **self.auth_header_for_user(url, user=machine.owner)) |
63 | |
64 | - self.assertEqual('"Success"', response.content) |
65 | + self.assertContains(response, 'Success') |
66 | updated = Machine.objects.get(uuid=machine.uuid, owner=machine.owner) |
67 | self.assertEqual(expected, updated.package_list) |
68 | |
69 | |
70 | === modified file 'src/webcatalog/tests/test_handlers.py' |
71 | --- src/webcatalog/tests/test_handlers.py 2011-07-28 18:49:26 +0000 |
72 | +++ src/webcatalog/tests/test_handlers.py 2011-07-29 15:07:55 +0000 |
73 | @@ -59,7 +59,7 @@ |
74 | class ListMachineHandlerTestCase(HandlerTestCase): |
75 | def test_no_machines_returns_empty_list(self): |
76 | handler = ListMachinesHandler() |
77 | - machines = handler.read(None) |
78 | + machines = handler.read(self.request) |
79 | self.assertEqual([], list(machines)) |
80 | |
81 | def test_multiple_machines_returns_as_expected(self): |
82 | @@ -67,12 +67,28 @@ |
83 | machine2 = self.factory.make_machine(owner=self.user) |
84 | handler = ListMachinesHandler() |
85 | |
86 | - machines = handler.read(None) |
87 | + machines = handler.read(self.request) |
88 | |
89 | self.assertEqual(2, len(machines)) |
90 | expected = set([machine1.uuid, machine2.uuid]) |
91 | self.assertEqual(expected, set(x.uuid for x in machines)) |
92 | |
93 | + def test_machine_for_user_other_than_the_first(self): |
94 | + """Check that we're not returning only machiens for the first user""" |
95 | + self.factory.make_user() |
96 | + self.factory.make_user() |
97 | + # Not the first user in the system: |
98 | + user = User.objects.all()[1] |
99 | + machine = self.factory.make_machine(owner=user) |
100 | + handler = ListMachinesHandler() |
101 | + request = HttpRequest() |
102 | + request.user = user |
103 | + |
104 | + machines = handler.read(request) |
105 | + |
106 | + self.assertEqual(1, len(machines)) |
107 | + self.assertEqual(machine.uuid, machines[0].uuid) |
108 | + |
109 | |
110 | class MachineHandlerTestCase(HandlerTestCase): |
111 | def test_read_invalid_uuid_returns_404(self): |
- 56. By Anthony Lenton
-
Minor changes per code review.
Ricardo Kirkner (ricardokirkner) wrote : | # |
Lovely!
Preview Diff
1 | === modified file 'src/webcatalog/admin.py' |
2 | --- src/webcatalog/admin.py 2011-07-02 04:53:26 +0000 |
3 | +++ src/webcatalog/admin.py 2011-07-29 15:59:29 +0000 |
4 | @@ -26,11 +26,13 @@ |
5 | Application, |
6 | Department, |
7 | DistroSeries, |
8 | + Machine, |
9 | ) |
10 | |
11 | __metaclass__ = type |
12 | __all__ = [ |
13 | 'ApplicationAdmin', |
14 | + 'MachineAdmin', |
15 | ] |
16 | |
17 | |
18 | @@ -40,6 +42,11 @@ |
19 | list_filter = ('distroseries', 'departments') |
20 | exclude = ('for_purchase', 'archive_id') |
21 | |
22 | +class MachineAdmin(admin.ModelAdmin): |
23 | + search_fields = ('owner__username', 'hostname', 'uuid') |
24 | + list_display = ('hostname', 'uuid', 'owner') |
25 | + |
26 | admin.site.register(Application, ApplicationAdmin) |
27 | admin.site.register(Department) |
28 | admin.site.register(DistroSeries) |
29 | +admin.site.register(Machine, MachineAdmin) |
30 | |
31 | === added file 'src/webcatalog/api/forms.py' |
32 | --- src/webcatalog/api/forms.py 1970-01-01 00:00:00 +0000 |
33 | +++ src/webcatalog/api/forms.py 2011-07-29 15:59:29 +0000 |
34 | @@ -0,0 +1,46 @@ |
35 | +# -*- coding: utf-8 -*- |
36 | +# This file is part of the Ubuntu Web Catalog |
37 | +# Copyright (C) 2011 Canonical Ltd. |
38 | +# |
39 | +# This program is free software: you can redistribute it and/or modify |
40 | +# it under the terms of the GNU Affero General Public License as |
41 | +# published by the Free Software Foundation, either version 3 of the |
42 | +# License, or (at your option) any later version. |
43 | +# |
44 | +# This program is distributed in the hope that it will be useful, |
45 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
46 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
47 | +# GNU Affero General Public License for more details. |
48 | +# |
49 | +# You should have received a copy of the GNU Affero General Public License |
50 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
51 | + |
52 | +"""Forms for using within the Ubuntu Web Catalog API.""" |
53 | + |
54 | +from __future__ import absolute_import |
55 | + |
56 | +__metaclass__ = type |
57 | +__all__ = [ |
58 | + 'MachineCreateUpdateForm', |
59 | + 'MachineUpdatePackagesForm', |
60 | +] |
61 | + |
62 | +from django import forms |
63 | + |
64 | +from webcatalog.models import Machine |
65 | + |
66 | +class MachineCreateUpdateForm(forms.ModelForm): |
67 | + class Meta: |
68 | + model = Machine |
69 | + fields = ( |
70 | + 'hostname', |
71 | + 'logo_checksum', |
72 | + ) |
73 | + |
74 | +class MachineUpdatePackagesForm(forms.ModelForm): |
75 | + class Meta: |
76 | + model = Machine |
77 | + fields = ( |
78 | + 'packages_checksum', |
79 | + 'package_list', |
80 | + ) |
81 | |
82 | === modified file 'src/webcatalog/api/handlers.py' |
83 | --- src/webcatalog/api/handlers.py 2011-06-29 11:40:16 +0000 |
84 | +++ src/webcatalog/api/handlers.py 2011-07-29 15:59:29 +0000 |
85 | @@ -24,11 +24,92 @@ |
86 | 'ServerStatusHandler', |
87 | ] |
88 | |
89 | +import json |
90 | + |
91 | +from django.contrib.auth.models import User |
92 | +from django.http import ( |
93 | + HttpResponse, |
94 | + HttpResponseBadRequest, |
95 | + HttpResponseNotFound, |
96 | + ) |
97 | from piston.handler import BaseHandler |
98 | |
99 | +from webcatalog.models import Machine |
100 | +from .forms import MachineCreateUpdateForm, MachineUpdatePackagesForm |
101 | + |
102 | class ServerStatusHandler(BaseHandler): |
103 | allowed_methods = ('GET',) |
104 | |
105 | def read(self, request): |
106 | return "ok" |
107 | |
108 | +class ListMachinesHandler(BaseHandler): |
109 | + allowed_methods = ('GET',) |
110 | + model = Machine |
111 | + fields = ('uuid', 'hostname', 'logo_checksum', 'packages_checksum') |
112 | + |
113 | + def read(self, request): |
114 | + result = Machine.objects.filter(owner=request.user) |
115 | + return result.defer('package_list') |
116 | + |
117 | +class MachineHandler(BaseHandler): |
118 | + allowed_methods = ('GET', 'POST', 'DELETE') |
119 | + model = Machine |
120 | + fields = ('uuid', 'hostname', 'logo_checksum', 'packages_checksum') |
121 | + |
122 | + def read(self, request, uuid): |
123 | + try: |
124 | + return Machine.objects.get(owner=request.user, uuid=uuid) |
125 | + except Machine.DoesNotExist: |
126 | + return HttpResponseNotFound('Invalid machine UUID') |
127 | + |
128 | + def create(self, request, uuid): |
129 | + if not hasattr(request, 'data'): |
130 | + return HttpResponseBadRequest("Unable to deserialize request") |
131 | + form = MachineCreateUpdateForm(request.data) |
132 | + if form.is_valid(): |
133 | + # Make this call work both for updating and creating machines |
134 | + instance, created = Machine.objects.get_or_create( |
135 | + owner=request.user, uuid=uuid) |
136 | + form = MachineCreateUpdateForm(request.data, instance=instance) |
137 | + return form.save() |
138 | + else: |
139 | + errors = dict((k, map(unicode, v)) |
140 | + for (k, v) in form.errors.items()) |
141 | + result = {'status': 'error', 'errors': errors} |
142 | + return result |
143 | + |
144 | + def delete(self, request, uuid): |
145 | + instances = Machine.objects.filter(owner=request.user, uuid=uuid) |
146 | + if instances.count() > 0: |
147 | + instances.delete() |
148 | + return HttpResponse(status=204) |
149 | + else: |
150 | + return HttpResponseNotFound('Invalid machine UUID') |
151 | + |
152 | + |
153 | +class PackagesHandler(BaseHandler): |
154 | + allowed_methods = ('GET', 'POST',) |
155 | + def read(self, request, uuid): |
156 | + try: |
157 | + instance = Machine.objects.get(owner=request.user, uuid=uuid) |
158 | + return instance.package_list |
159 | + except Machine.DoesNotExist: |
160 | + return HttpResponseNotFound('Invalid machine UUID') |
161 | + |
162 | + def create(self, request, uuid): |
163 | + try: |
164 | + instance = Machine.objects.get(owner=request.user, uuid=uuid) |
165 | + except Machine.DoesNotExist: |
166 | + return HttpResponseNotFound('Invalid machine UUID') |
167 | + if not hasattr(request, 'data'): |
168 | + return HttpResponseBadRequest("Unable to deserialize request") |
169 | + form = MachineUpdatePackagesForm(request.data, instance=instance) |
170 | + if form.is_valid(): |
171 | + form.save() |
172 | + return 'Success' |
173 | + else: |
174 | + errors = dict((k, map(unicode, v)) |
175 | + for (k, v) in form.errors.items()) |
176 | + result = {'status': 'error', 'errors': errors} |
177 | + return result |
178 | |
179 | === modified file 'src/webcatalog/api/urls.py' |
180 | --- src/webcatalog/api/urls.py 2011-06-29 11:40:16 +0000 |
181 | +++ src/webcatalog/api/urls.py 2011-07-29 15:59:29 +0000 |
182 | @@ -18,13 +18,25 @@ |
183 | |
184 | from piston.resource import Resource |
185 | from webcatalog.api.handlers import ( |
186 | + ListMachinesHandler, |
187 | + MachineHandler, |
188 | + PackagesHandler, |
189 | ServerStatusHandler, |
190 | ) |
191 | from webcatalog.auth import SSOOAuthAuthentication |
192 | |
193 | auth = SSOOAuthAuthentication(realm="Ubuntu Web Catalog") |
194 | |
195 | +class CSRFExemptResource(Resource): |
196 | + """A Custom Resource that is csrf exempt""" |
197 | + def __init__(self, handler, authentication=None): |
198 | + super(CSRFExemptResource, self).__init__(handler, authentication) |
199 | + self.csrf_exempt = True |
200 | + |
201 | server_status_resource = Resource(handler=ServerStatusHandler) |
202 | +list_machines_resource = Resource(handler=ListMachinesHandler, authentication=auth) |
203 | +machine_resource = CSRFExemptResource(handler=MachineHandler, authentication=auth) |
204 | +packages_resource = CSRFExemptResource(handler=PackagesHandler, authentication=auth) |
205 | |
206 | urlpatterns = patterns('', |
207 | # get status of the service (usually just "ok", might be "read-only") |
208 | @@ -32,6 +44,12 @@ |
209 | # send a moderation request |
210 | # GET /1.0/server-status/ |
211 | url(r'^1.0/server-status/$', server_status_resource, |
212 | - name='server-status'), |
213 | - |
214 | + name='wb-server-status'), |
215 | + # GET /1.0/list-machines/ |
216 | + url(r'^1.0/list-machines/$', list_machines_resource, |
217 | + name='wb-list-machines'), |
218 | + url(r'^1.0/machine/(?P<uuid>[-\w]+)/$', |
219 | + machine_resource, name='wb-machine'), |
220 | + url(r'^1.0/packages/(?P<uuid>[-\w]+)/$', |
221 | + packages_resource, name='wb-packages'), |
222 | ) |
223 | |
224 | === added file 'src/webcatalog/migrations/0007_add_machine.py' |
225 | --- src/webcatalog/migrations/0007_add_machine.py 1970-01-01 00:00:00 +0000 |
226 | +++ src/webcatalog/migrations/0007_add_machine.py 2011-07-29 15:59:29 +0000 |
227 | @@ -0,0 +1,136 @@ |
228 | +# encoding: utf-8 |
229 | +import datetime |
230 | +from south.db import db |
231 | +from south.v2 import SchemaMigration |
232 | +from django.db import models |
233 | + |
234 | +class Migration(SchemaMigration): |
235 | + |
236 | + def forwards(self, orm): |
237 | + |
238 | + # Adding model 'Machine' |
239 | + db.create_table('webcatalog_machine', ( |
240 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
241 | + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), |
242 | + ('uuid', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)), |
243 | + ('hostname', self.gf('django.db.models.fields.CharField')(max_length=64)), |
244 | + ('packages_checksum', self.gf('django.db.models.fields.CharField')(max_length=56)), |
245 | + ('package_list', self.gf('django.db.models.fields.TextField')()), |
246 | + ('logo_checksum', self.gf('django.db.models.fields.CharField')(max_length=56, blank=True)), |
247 | + )) |
248 | + db.send_create_signal('webcatalog', ['Machine']) |
249 | + |
250 | + # Adding unique constraint on 'Machine', fields ['owner', 'uuid'] |
251 | + db.create_unique('webcatalog_machine', ['owner_id', 'uuid']) |
252 | + |
253 | + # Adding unique constraint on 'ReviewStatsImport', fields ['distroseries'] |
254 | + db.create_unique('webcatalog_reviewstatsimport', ['distroseries_id']) |
255 | + |
256 | + |
257 | + def backwards(self, orm): |
258 | + |
259 | + # Removing unique constraint on 'ReviewStatsImport', fields ['distroseries'] |
260 | + db.delete_unique('webcatalog_reviewstatsimport', ['distroseries_id']) |
261 | + |
262 | + # Removing unique constraint on 'Machine', fields ['owner', 'uuid'] |
263 | + db.delete_unique('webcatalog_machine', ['owner_id', 'uuid']) |
264 | + |
265 | + # Deleting model 'Machine' |
266 | + db.delete_table('webcatalog_machine') |
267 | + |
268 | + |
269 | + models = { |
270 | + 'auth.group': { |
271 | + 'Meta': {'object_name': 'Group'}, |
272 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
273 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
274 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
275 | + }, |
276 | + 'auth.permission': { |
277 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
278 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
279 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
280 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
281 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
282 | + }, |
283 | + 'auth.user': { |
284 | + 'Meta': {'object_name': 'User'}, |
285 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
286 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
287 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
288 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
289 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
290 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
291 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
292 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
293 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
294 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
295 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
296 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
297 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
298 | + }, |
299 | + 'contenttypes.contenttype': { |
300 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
301 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
302 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
303 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
304 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
305 | + }, |
306 | + 'webcatalog.application': { |
307 | + 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'}, |
308 | + 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
309 | + 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
310 | + 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}), |
311 | + 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
312 | + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
313 | + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
314 | + 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}), |
315 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
316 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
317 | + 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
318 | + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
319 | + 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
320 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
321 | + 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
322 | + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}), |
323 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
324 | + 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
325 | + 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
326 | + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}), |
327 | + 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}), |
328 | + 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), |
329 | + 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
330 | + 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
331 | + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}) |
332 | + }, |
333 | + 'webcatalog.department': { |
334 | + 'Meta': {'object_name': 'Department'}, |
335 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
336 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
337 | + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}) |
338 | + }, |
339 | + 'webcatalog.distroseries': { |
340 | + 'Meta': {'object_name': 'DistroSeries'}, |
341 | + 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), |
342 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
343 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}) |
344 | + }, |
345 | + 'webcatalog.machine': { |
346 | + 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'}, |
347 | + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
348 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
349 | + 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}), |
350 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), |
351 | + 'package_list': ('django.db.models.fields.TextField', [], {}), |
352 | + 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}), |
353 | + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}) |
354 | + }, |
355 | + 'webcatalog.reviewstatsimport': { |
356 | + 'Meta': {'object_name': 'ReviewStatsImport'}, |
357 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}), |
358 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
359 | + 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
360 | + } |
361 | + } |
362 | + |
363 | + complete_apps = ['webcatalog'] |
364 | |
365 | === added file 'src/webcatalog/migrations/0008_add_oauth_tables.py' |
366 | --- src/webcatalog/migrations/0008_add_oauth_tables.py 1970-01-01 00:00:00 +0000 |
367 | +++ src/webcatalog/migrations/0008_add_oauth_tables.py 2011-07-29 15:59:29 +0000 |
368 | @@ -0,0 +1,176 @@ |
369 | +# encoding: utf-8 |
370 | +import datetime |
371 | +from south.db import db |
372 | +from south.v2 import SchemaMigration |
373 | +from django.db import models |
374 | + |
375 | +class Migration(SchemaMigration): |
376 | + |
377 | + def forwards(self, orm): |
378 | + |
379 | + # Adding model 'Consumer' |
380 | + db.create_table('webcatalog_consumer', ( |
381 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
382 | + ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='oauth_consumer', unique=True, to=orm['auth.User'])), |
383 | + ('key', self.gf('django.db.models.fields.CharField')(max_length=64)), |
384 | + ('secret', self.gf('django.db.models.fields.CharField')(default='PxDdsAQWcEFXUXmyQfsceNTJmgxcsQ', max_length=255, blank=True)), |
385 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
386 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
387 | + )) |
388 | + db.send_create_signal('webcatalog', ['Consumer']) |
389 | + |
390 | + # Adding model 'Token' |
391 | + db.create_table('webcatalog_token', ( |
392 | + ('consumer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['webcatalog.Consumer'])), |
393 | + ('token', self.gf('django.db.models.fields.CharField')(default='rLuJPRLPHKDpxPqeaiytxwZctoWUdjRfbUvinaCSKrsTyZWGsV', max_length=50, primary_key=True)), |
394 | + ('token_secret', self.gf('django.db.models.fields.CharField')(default='UoHiMuBdlsZxAZYHrDCnJnEnBJBKrTmZXGsqlMGXFENLtXeAvq', max_length=50)), |
395 | + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), |
396 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
397 | + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
398 | + )) |
399 | + db.send_create_signal('webcatalog', ['Token']) |
400 | + |
401 | + # Adding model 'Nonce' |
402 | + db.create_table('webcatalog_nonce', ( |
403 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
404 | + ('token', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['webcatalog.Token'])), |
405 | + ('consumer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['webcatalog.Consumer'])), |
406 | + ('nonce', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), |
407 | + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), |
408 | + )) |
409 | + db.send_create_signal('webcatalog', ['Nonce']) |
410 | + |
411 | + |
412 | + def backwards(self, orm): |
413 | + |
414 | + # Deleting model 'Consumer' |
415 | + db.delete_table('webcatalog_consumer') |
416 | + |
417 | + # Deleting model 'Token' |
418 | + db.delete_table('webcatalog_token') |
419 | + |
420 | + # Deleting model 'Nonce' |
421 | + db.delete_table('webcatalog_nonce') |
422 | + |
423 | + |
424 | + models = { |
425 | + 'auth.group': { |
426 | + 'Meta': {'object_name': 'Group'}, |
427 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
428 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
429 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
430 | + }, |
431 | + 'auth.permission': { |
432 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
433 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
434 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
435 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
436 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
437 | + }, |
438 | + 'auth.user': { |
439 | + 'Meta': {'object_name': 'User'}, |
440 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
441 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
442 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
443 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
444 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
445 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
446 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
447 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
448 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
449 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
450 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
451 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
452 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
453 | + }, |
454 | + 'contenttypes.contenttype': { |
455 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
456 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
457 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
458 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
459 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
460 | + }, |
461 | + 'webcatalog.application': { |
462 | + 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'}, |
463 | + 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
464 | + 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
465 | + 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}), |
466 | + 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
467 | + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
468 | + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
469 | + 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}), |
470 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
471 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
472 | + 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
473 | + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
474 | + 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
475 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
476 | + 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
477 | + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}), |
478 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
479 | + 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
480 | + 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
481 | + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}), |
482 | + 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}), |
483 | + 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), |
484 | + 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
485 | + 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
486 | + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}) |
487 | + }, |
488 | + 'webcatalog.consumer': { |
489 | + 'Meta': {'object_name': 'Consumer'}, |
490 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
491 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
492 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
493 | + 'secret': ('django.db.models.fields.CharField', [], {'default': "'hFuSuGGcaWAcAgjRlcWbIuHVTutivv'", 'max_length': '255', 'blank': 'True'}), |
494 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
495 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"}) |
496 | + }, |
497 | + 'webcatalog.department': { |
498 | + 'Meta': {'object_name': 'Department'}, |
499 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
500 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
501 | + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}) |
502 | + }, |
503 | + 'webcatalog.distroseries': { |
504 | + 'Meta': {'object_name': 'DistroSeries'}, |
505 | + 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), |
506 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
507 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}) |
508 | + }, |
509 | + 'webcatalog.machine': { |
510 | + 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'}, |
511 | + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
512 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
513 | + 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}), |
514 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), |
515 | + 'package_list': ('django.db.models.fields.TextField', [], {}), |
516 | + 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}), |
517 | + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}) |
518 | + }, |
519 | + 'webcatalog.nonce': { |
520 | + 'Meta': {'object_name': 'Nonce'}, |
521 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}), |
522 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
523 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
524 | + 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
525 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"}) |
526 | + }, |
527 | + 'webcatalog.reviewstatsimport': { |
528 | + 'Meta': {'object_name': 'ReviewStatsImport'}, |
529 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}), |
530 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
531 | + 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
532 | + }, |
533 | + 'webcatalog.token': { |
534 | + 'Meta': {'object_name': 'Token'}, |
535 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}), |
536 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
537 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
538 | + 'token': ('django.db.models.fields.CharField', [], {'default': "'BzXRMTLNLbCqttuBrpPirPQnsUJNctCyykYYhMruecOojcBlGf'", 'max_length': '50', 'primary_key': 'True'}), |
539 | + 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'aYiLvMWQoXqKdlQXJSPFgRLXnQxUHpfXHdpdSLKCcfmMZRWMNw'", 'max_length': '50'}), |
540 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
541 | + } |
542 | + } |
543 | + |
544 | + complete_apps = ['webcatalog'] |
545 | |
546 | === modified file 'src/webcatalog/models/__init__.py' |
547 | --- src/webcatalog/models/__init__.py 2011-07-19 12:42:03 +0000 |
548 | +++ src/webcatalog/models/__init__.py 2011-07-29 15:59:29 +0000 |
549 | @@ -27,6 +27,7 @@ |
550 | 'Application', |
551 | 'Department', |
552 | 'ReviewStatsImport', |
553 | + 'Machine', |
554 | ] |
555 | |
556 | from .oauthtoken import Token, Consumer, Nonce, DataStore |
557 | @@ -34,5 +35,6 @@ |
558 | Application, |
559 | Department, |
560 | DistroSeries, |
561 | + Machine, |
562 | ReviewStatsImport, |
563 | ) |
564 | |
565 | === modified file 'src/webcatalog/models/applications.py' |
566 | --- src/webcatalog/models/applications.py 2011-07-20 15:27:06 +0000 |
567 | +++ src/webcatalog/models/applications.py 2011-07-29 15:59:29 +0000 |
568 | @@ -26,6 +26,7 @@ |
569 | import re |
570 | from datetime import datetime |
571 | |
572 | +from django.contrib.auth.models import User |
573 | from django.core.urlresolvers import reverse |
574 | from django.db import models |
575 | |
576 | @@ -34,7 +35,9 @@ |
577 | __metaclass__ = type |
578 | __all__ = [ |
579 | 'Application', |
580 | + 'Department', |
581 | 'DistroSeries', |
582 | + 'Machine', |
583 | 'ReviewStatsImport', |
584 | ] |
585 | |
586 | @@ -183,6 +186,17 @@ |
587 | class ReviewStatsImport(models.Model): |
588 | distroseries = models.ForeignKey(DistroSeries, unique=True) |
589 | last_import = models.DateTimeField(default=datetime.utcnow) |
590 | - |
591 | - class Meta: |
592 | - app_label = 'webcatalog' |
593 | + class Meta: |
594 | + app_label = 'webcatalog' |
595 | + |
596 | + |
597 | +class Machine(models.Model): |
598 | + owner = models.ForeignKey(User, db_index=True) |
599 | + uuid = models.CharField(max_length=32, db_index=True) |
600 | + hostname = models.CharField(max_length=64) |
601 | + packages_checksum = models.CharField(max_length=56) |
602 | + package_list = models.TextField() |
603 | + logo_checksum = models.CharField(max_length=56, blank=True) |
604 | + class Meta: |
605 | + app_label = 'webcatalog' |
606 | + unique_together = ('owner', 'uuid') |
607 | |
608 | === modified file 'src/webcatalog/models/oauthtoken.py' |
609 | --- src/webcatalog/models/oauthtoken.py 2011-06-27 16:31:36 +0000 |
610 | +++ src/webcatalog/models/oauthtoken.py 2011-07-29 15:59:29 +0000 |
611 | @@ -84,7 +84,7 @@ |
612 | return self.token |
613 | |
614 | class Meta: |
615 | - app_label = 'reviewsapp' |
616 | + app_label = 'webcatalog' |
617 | |
618 | |
619 | class Consumer(models.Model): |
620 | @@ -130,7 +130,7 @@ |
621 | return OAuthConsumer(self.key, self.secret) |
622 | |
623 | class Meta: |
624 | - app_label = 'reviewsapp' |
625 | + app_label = 'webcatalog' |
626 | |
627 | |
628 | class Nonce(models.Model): |
629 | @@ -149,7 +149,7 @@ |
630 | return consumer.nonce_set.create(token=token, nonce=nonce) |
631 | |
632 | class Meta: |
633 | - app_label = 'reviewsapp' |
634 | + app_label = 'webcatalog' |
635 | |
636 | class DataStore(OAuthDataStore): |
637 | |
638 | |
639 | === modified file 'src/webcatalog/schema.py' |
640 | --- src/webcatalog/schema.py 2011-07-19 15:44:09 +0000 |
641 | +++ src/webcatalog/schema.py 2011-07-29 15:59:29 +0000 |
642 | @@ -49,6 +49,9 @@ |
643 | webcatalog.disk_apt_cache_location = StringConfigOption() |
644 | webcatalog.default_distro = StringConfigOption() |
645 | webcatalog.page_batch_size = IntConfigOption(default=20) |
646 | + webcatalog.preload_api_service_roots = BoolConfigOption() |
647 | + webcatalog.oauth_data_store = StringConfigOption( |
648 | + default='webcatalog.models.oauthtoken.DataStore') |
649 | |
650 | google = ConfigSection() |
651 | google.google_analytics_id = StringConfigOption() |
652 | |
653 | === modified file 'src/webcatalog/tests/__init__.py' |
654 | --- src/webcatalog/tests/__init__.py 2011-06-30 17:18:27 +0000 |
655 | +++ src/webcatalog/tests/__init__.py 2011-07-29 15:59:29 +0000 |
656 | @@ -16,10 +16,16 @@ |
657 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
658 | |
659 | """Import various view, model and other tests for django's default runner.""" |
660 | +from .test_api import * |
661 | from .test_commands import * |
662 | from .test_department_filters import * |
663 | from .test_forms import * |
664 | +from .test_handlers import * |
665 | from .test_models import * |
666 | from .test_templatetags import * |
667 | from .test_utilities import * |
668 | from .test_views import * |
669 | + |
670 | +# disable logging when running tests |
671 | +import logging |
672 | +logging.disable(logging.CRITICAL) |
673 | |
674 | === modified file 'src/webcatalog/tests/factory.py' |
675 | --- src/webcatalog/tests/factory.py 2011-07-21 13:18:39 +0000 |
676 | +++ src/webcatalog/tests/factory.py 2011-07-29 15:59:29 +0000 |
677 | @@ -26,12 +26,17 @@ |
678 | |
679 | from django.contrib.auth.models import User |
680 | from django.test import TestCase |
681 | +from django_openid_auth.models import UserOpenID |
682 | |
683 | from webcatalog.models import ( |
684 | Application, |
685 | + Consumer, |
686 | Department, |
687 | DistroSeries, |
688 | + Machine, |
689 | + Token, |
690 | ) |
691 | +from webcatalog.utilities import full_claimed_id |
692 | |
693 | __metaclass__ = type |
694 | __all__ = [ |
695 | @@ -74,6 +79,16 @@ |
696 | user.is_superuser = True |
697 | user.save() |
698 | |
699 | + # Create an openid record too. |
700 | + if open_id is None: |
701 | + open_id = full_claimed_id(self.get_unique_string(prefix='ident-')) |
702 | + elif open_id is False: |
703 | + return user |
704 | + useropenid = UserOpenID.objects.create( |
705 | + user=user, claimed_id=open_id, display_id=open_id) |
706 | + |
707 | + return user |
708 | + |
709 | def make_application(self, package_name=None, name=None, |
710 | comment=None, description=None, icon_name='', icon=None, |
711 | distroseries=None, arch='i686', ratings_average=None, |
712 | @@ -112,6 +127,43 @@ |
713 | return os.path.join( |
714 | os.path.dirname(__file__), 'test_data', file_name) |
715 | |
716 | + def make_machine(self, owner=None, uuid=None, hostname=None, |
717 | + package_list=None): |
718 | + if owner is None: |
719 | + owner = self.make_user() |
720 | + if hostname is None: |
721 | + hostname = self.get_unique_string(prefix='hostname-') |
722 | + if uuid is None: |
723 | + uuid = self.get_unique_string(prefix='uuid-') |
724 | + if package_list is None: |
725 | + package_list = self.get_unique_string(prefix='package-list-') |
726 | + packages_checksum = self.get_unique_string(prefix='package-checksum-') |
727 | + logo_checksum = self.get_unique_string(prefix='logo-checksum-') |
728 | + |
729 | + return Machine.objects.create(owner=owner, hostname=hostname, |
730 | + uuid=uuid, packages_checksum=packages_checksum, |
731 | + package_list=package_list, logo_checksum=logo_checksum) |
732 | + |
733 | + def make_oauth_token_and_consumer(self, user=None, save=True): |
734 | + """Create a new set of OAuth token and consumer creds.""" |
735 | + if user is None: |
736 | + user = self.make_user() |
737 | + |
738 | + consumer_key = user.useropenid_set.get().claimed_id.split('/')[-1] |
739 | + consumer_secret = self.get_unique_string(prefix='consumer-secret-') |
740 | + consumer = Consumer(user=user, key=consumer_key, |
741 | + secret=consumer_secret) |
742 | + if save: |
743 | + consumer.save() |
744 | + token_string = self.get_unique_string(prefix='token-') |
745 | + token_secret = self.get_unique_string(prefix='token-secret-') |
746 | + token_name = self.get_unique_string(prefix='token-name-') |
747 | + token = Token(consumer=consumer, token=token_string, |
748 | + token_secret=token_secret, name=token_name) |
749 | + if save: |
750 | + token.save() |
751 | + return token, consumer |
752 | + |
753 | |
754 | class TestCaseWithFactory(TestCase): |
755 | |
756 | |
757 | === added file 'src/webcatalog/tests/test_api.py' |
758 | --- src/webcatalog/tests/test_api.py 1970-01-01 00:00:00 +0000 |
759 | +++ src/webcatalog/tests/test_api.py 2011-07-29 15:59:29 +0000 |
760 | @@ -0,0 +1,254 @@ |
761 | +# -*- coding: utf-8 -*- |
762 | +# This file is part of the Ubuntu Web Catalog |
763 | +# Copyright (C) 2011 Canonical Ltd. |
764 | +# |
765 | +# This program is free software: you can redistribute it and/or modify |
766 | +# it under the terms of the GNU Affero General Public License as |
767 | +# published by the Free Software Foundation, either version 3 of the |
768 | +# License, or (at your option) any later version. |
769 | +# |
770 | +# This program is distributed in the hope that it will be useful, |
771 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
772 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
773 | +# GNU Affero General Public License for more details. |
774 | +# |
775 | +# You should have received a copy of the GNU Affero General Public License |
776 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
777 | + |
778 | +"""WebCatalog API tests.""" |
779 | + |
780 | +from __future__ import absolute_import |
781 | + |
782 | +__metaclass__ = type |
783 | +__all__ = [ |
784 | + 'DeleteMachineTestCase', |
785 | + 'GetMachineTestCase', |
786 | + 'ListPackagesTestCase', |
787 | + 'ListMachinesTestCase', |
788 | + 'ServerStatusTestCase', |
789 | + 'UpdateMachineTestCase', |
790 | + 'UpdatePackageListTestCase', |
791 | + ] |
792 | + |
793 | +from django.utils import simplejson |
794 | + |
795 | +from django.test import TestCase |
796 | +from oauth.oauth import ( |
797 | + OAuthRequest, |
798 | + OAuthConsumer, |
799 | + OAuthToken, |
800 | + OAuthSignatureMethod_PLAINTEXT, |
801 | + ) |
802 | + |
803 | +from .factory import TestCaseWithFactory |
804 | +from webcatalog.models import Machine |
805 | + |
806 | +class ServerStatusTestCase(TestCase): |
807 | + def test_server_status(self): |
808 | + response = self.client.get('/cat/api/1.0/server-status/') |
809 | + self.assertEqual(response.content, '"ok"') |
810 | + |
811 | +class AuthenticatedAPITestCase(TestCaseWithFactory): |
812 | + def auth_header_for_user(self, url, user=None, realm='OAuth'): |
813 | + token, consumer = self.factory.make_oauth_token_and_consumer(user=user) |
814 | + oaconsumer = OAuthConsumer(token.consumer.key, token.consumer.secret) |
815 | + oatoken = OAuthToken(token.token, token.token_secret) |
816 | + oarequest = OAuthRequest.from_consumer_and_token( |
817 | + oaconsumer, oatoken, http_url=url) |
818 | + oarequest.sign_request(OAuthSignatureMethod_PLAINTEXT(), |
819 | + oaconsumer, oatoken) |
820 | + header = oarequest.to_header(realm) |
821 | + return {'HTTP_AUTHORIZATION': header['Authorization']} |
822 | + |
823 | + |
824 | +class ListMachinesTestCase(AuthenticatedAPITestCase): |
825 | + url = '/cat/api/1.0/list-machines/' |
826 | + def test_no_auth_returns_401(self): |
827 | + response = self.client.get(self.url) |
828 | + self.assertEqual(401, response.status_code) |
829 | + |
830 | + def test_read_no_machines(self): |
831 | + user = self.factory.make_user() |
832 | + response = self.client.get(self.url, |
833 | + **self.auth_header_for_user(self.url, user=user)) |
834 | + self.assertEqual('[]', response.content) |
835 | + |
836 | + def test_read_multiple_machines(self): |
837 | + user = self.factory.make_user() |
838 | + machine1 = self.factory.make_machine(owner=user) |
839 | + machine2 = self.factory.make_machine(owner=user) |
840 | + response = self.client.get(self.url, |
841 | + **self.auth_header_for_user(self.url, user=user)) |
842 | + data = simplejson.loads(response.content) |
843 | + self.assertEqual(2, len(data)) |
844 | + expected = set([machine1.uuid, machine2.uuid]) |
845 | + self.assertEqual(expected, set([x['uuid'] for x in data])) |
846 | + |
847 | + def test_only_returns_machines_for_the_authenticated_user(self): |
848 | + mymachine = self.factory.make_machine() |
849 | + othermachine = self.factory.make_machine() |
850 | + response = self.client.get(self.url, |
851 | + **self.auth_header_for_user(self.url, user=mymachine.owner)) |
852 | + data = simplejson.loads(response.content) |
853 | + |
854 | + self.assertEqual(1, len(data)) |
855 | + self.assertEqual(mymachine.uuid, data[0]['uuid']) |
856 | + |
857 | + |
858 | +class UpdateMachineTestCase(AuthenticatedAPITestCase): |
859 | + url = '/cat/api/1.0/machine/%s/' |
860 | + |
861 | + def test_no_auth_returns_401(self): |
862 | + machine = self.factory.make_machine() |
863 | + response = self.client.get(self.url % machine.uuid) |
864 | + self.assertEqual(401, response.status_code) |
865 | + |
866 | + def test_update_success(self): |
867 | + machine = self.factory.make_machine() |
868 | + data = simplejson.dumps({'hostname': machine.hostname + '-updated'}) |
869 | + url = self.url % machine.uuid |
870 | + |
871 | + response = self.client.post(url, data=data, |
872 | + content_type='application/json', |
873 | + **self.auth_header_for_user(url, user=machine.owner)) |
874 | + |
875 | + data = simplejson.loads(response.content) |
876 | + self.assertEqual(machine.hostname + '-updated', data['hostname']) |
877 | + |
878 | + def test_creates_new_machine_if_not_owner(self): |
879 | + machine = self.factory.make_machine() |
880 | + otheruser = self.factory.make_user() |
881 | + data = simplejson.dumps({'hostname': machine.hostname + '-updated'}) |
882 | + url = self.url % machine.uuid |
883 | + |
884 | + response = self.client.post(url, data=data, |
885 | + content_type='application/json', |
886 | + **self.auth_header_for_user(url, user=otheruser)) |
887 | + |
888 | + data = simplejson.loads(response.content) |
889 | + self.assertEqual('', data['packages_checksum']) |
890 | + |
891 | + |
892 | +class DeleteMachineTestCase(AuthenticatedAPITestCase): |
893 | + url = '/cat/api/1.0/machine/%s/' |
894 | + |
895 | + def test_no_auth_returns_401(self): |
896 | + machine = self.factory.make_machine() |
897 | + response = self.client.delete(self.url % machine.uuid) |
898 | + self.assertEqual(401, response.status_code) |
899 | + |
900 | + def test_delete_success(self): |
901 | + machine = self.factory.make_machine() |
902 | + url = self.url % machine.uuid |
903 | + |
904 | + response = self.client.delete(url, |
905 | + **self.auth_header_for_user(url, user=machine.owner)) |
906 | + |
907 | + self.assertEqual(204, response.status_code) |
908 | + self.assertRaises(Machine.DoesNotExist, Machine.objects.get, |
909 | + uuid=machine.uuid, owner=machine.owner) |
910 | + |
911 | + def test_delete_other_users_machine_fails(self): |
912 | + machine = self.factory.make_machine() |
913 | + otheruser = self.factory.make_user() |
914 | + url = self.url % machine.uuid |
915 | + |
916 | + response = self.client.delete(url, |
917 | + **self.auth_header_for_user(url, user=otheruser)) |
918 | + |
919 | + self.assertEqual(404, response.status_code) |
920 | + |
921 | + |
922 | +class GetMachineTestCase(AuthenticatedAPITestCase): |
923 | + url = '/cat/api/1.0/machine/%s/' |
924 | + |
925 | + def test_no_auth_returns_401(self): |
926 | + machine = self.factory.make_machine() |
927 | + response = self.client.get(self.url % machine.uuid) |
928 | + self.assertEqual(401, response.status_code) |
929 | + |
930 | + def test_get_success(self): |
931 | + machine = self.factory.make_machine() |
932 | + url = self.url % machine.uuid |
933 | + |
934 | + response = self.client.get(url, |
935 | + **self.auth_header_for_user(url, user=machine.owner)) |
936 | + |
937 | + data = simplejson.loads(response.content) |
938 | + self.assertEqual(machine.hostname, data['hostname']) |
939 | + self.assertEqual(machine.logo_checksum, data['logo_checksum']) |
940 | + |
941 | + def test_get_other_users_machine_fails(self): |
942 | + machine = self.factory.make_machine() |
943 | + otheruser = self.factory.make_user() |
944 | + url = self.url % machine.uuid |
945 | + |
946 | + response = self.client.get(url, |
947 | + **self.auth_header_for_user(url, user=otheruser)) |
948 | + |
949 | + self.assertEqual(404, response.status_code) |
950 | + |
951 | + |
952 | +class ListPackagesTestCase(AuthenticatedAPITestCase): |
953 | + url = '/cat/api/1.0/packages/%s/' |
954 | + |
955 | + def test_no_auth_returns_401(self): |
956 | + machine = self.factory.make_machine() |
957 | + response = self.client.get(self.url % machine.uuid) |
958 | + self.assertEqual(401, response.status_code) |
959 | + |
960 | + def test_get_success(self): |
961 | + expected = 'some-random-package-list' |
962 | + machine = self.factory.make_machine(package_list=expected) |
963 | + url = self.url % machine.uuid |
964 | + |
965 | + response = self.client.get(url, |
966 | + **self.auth_header_for_user(url, user=machine.owner)) |
967 | + |
968 | + data = simplejson.loads(response.content) |
969 | + self.assertEqual(expected, data) |
970 | + |
971 | + def test_get_other_users_package_list_fails(self): |
972 | + machine = self.factory.make_machine() |
973 | + otheruser = self.factory.make_user() |
974 | + url = self.url % machine.uuid |
975 | + |
976 | + response = self.client.get(url, |
977 | + **self.auth_header_for_user(url, user=otheruser)) |
978 | + |
979 | + self.assertEqual(404, response.status_code) |
980 | + |
981 | + |
982 | +class UpdatePackageListTestCase(AuthenticatedAPITestCase): |
983 | + url = '/cat/api/1.0/packages/%s/' |
984 | + |
985 | + def test_no_auth_returns_401(self): |
986 | + machine = self.factory.make_machine() |
987 | + response = self.client.post(self.url % machine.uuid) |
988 | + self.assertEqual(401, response.status_code) |
989 | + |
990 | + def test_post_success(self): |
991 | + expected = 'some-random-package-list' |
992 | + machine = self.factory.make_machine(package_list=expected) |
993 | + url = self.url % machine.uuid |
994 | + data = simplejson.dumps({'package_list': expected, |
995 | + 'packages_checksum': 'foo'}) |
996 | + |
997 | + response = self.client.post(url, data=data, |
998 | + content_type='application/json', |
999 | + **self.auth_header_for_user(url, user=machine.owner)) |
1000 | + |
1001 | + self.assertContains(response, 'Success') |
1002 | + updated = Machine.objects.get(uuid=machine.uuid, owner=machine.owner) |
1003 | + self.assertEqual(expected, updated.package_list) |
1004 | + |
1005 | + def test_get_other_users_package_list_fails(self): |
1006 | + machine = self.factory.make_machine() |
1007 | + otheruser = self.factory.make_user() |
1008 | + url = self.url % machine.uuid |
1009 | + |
1010 | + response = self.client.post(url, data='"foo"', |
1011 | + content_type='application/json', |
1012 | + **self.auth_header_for_user(url, user=otheruser)) |
1013 | + |
1014 | + self.assertEqual(404, response.status_code) |
1015 | |
1016 | === modified file 'src/webcatalog/tests/test_handlers.py' |
1017 | --- src/webcatalog/tests/test_handlers.py 2011-06-29 11:40:16 +0000 |
1018 | +++ src/webcatalog/tests/test_handlers.py 2011-07-29 15:59:29 +0000 |
1019 | @@ -21,17 +21,204 @@ |
1020 | |
1021 | __metaclass__ = type |
1022 | __all__ = [ |
1023 | + 'ListMachineHandlerTestCase', |
1024 | + 'MachineHandlerTestCase', |
1025 | + 'PackagesHandlerTestCase', |
1026 | 'ServerStatusHandlerTestCase', |
1027 | ] |
1028 | |
1029 | |
1030 | +from django.http import HttpRequest |
1031 | from django.test import TestCase |
1032 | +from django.contrib.auth.models import User |
1033 | |
1034 | -from reviewsapp.api.handlers import ( |
1035 | +from webcatalog.models import Machine |
1036 | +from webcatalog.api.handlers import ( |
1037 | + ListMachinesHandler, |
1038 | + MachineHandler, |
1039 | + PackagesHandler, |
1040 | ServerStatusHandler, |
1041 | ) |
1042 | +from webcatalog.tests.factory import TestCaseWithFactory |
1043 | + |
1044 | |
1045 | class ServerStatusHandlerTestCase(TestCase): |
1046 | def test_read(self): |
1047 | ss_handler = ServerStatusHandler() |
1048 | self.assertEqual('ok', ss_handler.read(None)) |
1049 | + |
1050 | + |
1051 | +class HandlerTestCase(TestCaseWithFactory): |
1052 | + def setUp(self): |
1053 | + super(HandlerTestCase, self).setUp() |
1054 | + self.user = self.factory.make_user() |
1055 | + self.request = HttpRequest() |
1056 | + self.request.user = self.user |
1057 | + |
1058 | + |
1059 | +class ListMachineHandlerTestCase(HandlerTestCase): |
1060 | + def test_no_machines_returns_empty_list(self): |
1061 | + handler = ListMachinesHandler() |
1062 | + machines = handler.read(self.request) |
1063 | + self.assertEqual([], list(machines)) |
1064 | + |
1065 | + def test_multiple_machines_returns_as_expected(self): |
1066 | + machine1 = self.factory.make_machine(owner=self.user) |
1067 | + machine2 = self.factory.make_machine(owner=self.user) |
1068 | + handler = ListMachinesHandler() |
1069 | + |
1070 | + machines = handler.read(self.request) |
1071 | + |
1072 | + self.assertEqual(2, len(machines)) |
1073 | + expected = set([machine1.uuid, machine2.uuid]) |
1074 | + self.assertEqual(expected, set(x.uuid for x in machines)) |
1075 | + |
1076 | + def test_machine_for_user_other_than_the_first(self): |
1077 | + """Check that we're not returning only machiens for the first user""" |
1078 | + self.factory.make_user() |
1079 | + self.factory.make_user() |
1080 | + # Not the first user in the system: |
1081 | + user = User.objects.all()[1] |
1082 | + machine = self.factory.make_machine(owner=user) |
1083 | + handler = ListMachinesHandler() |
1084 | + request = HttpRequest() |
1085 | + request.user = user |
1086 | + |
1087 | + machines = handler.read(request) |
1088 | + |
1089 | + self.assertEqual(1, len(machines)) |
1090 | + self.assertEqual(machine.uuid, machines[0].uuid) |
1091 | + |
1092 | + |
1093 | +class MachineHandlerTestCase(HandlerTestCase): |
1094 | + def test_read_invalid_uuid_returns_404(self): |
1095 | + handler = MachineHandler() |
1096 | + response = handler.read(self.request, self.factory.get_unique_string()) |
1097 | + self.assertEqual(404, response.status_code) |
1098 | + |
1099 | + def test_read(self): |
1100 | + handler = MachineHandler() |
1101 | + machine = self.factory.make_machine(self.user) |
1102 | + returned = handler.read(self.request, machine.uuid) |
1103 | + self.assertEqual(machine.uuid, returned.uuid) |
1104 | + |
1105 | + def test_create_no_data(self): |
1106 | + """Test the case where no data was deserialized""" |
1107 | + request = HttpRequest() |
1108 | + handler = MachineHandler() |
1109 | + response = handler.create(request, uuid='foo') |
1110 | + self.assertContains(response, "Unable to deserialize request", |
1111 | + status_code=400) |
1112 | + |
1113 | + def test_create_missing_hostname(self): |
1114 | + request = HttpRequest() |
1115 | + request.data = {'logo_checksum': 'bar'} |
1116 | + handler = MachineHandler() |
1117 | + response = handler.create(request, 'uuid') |
1118 | + expected = {'status': 'error', 'errors': |
1119 | + {'hostname': [u'This field is required.']}} |
1120 | + self.assertEqual(expected, response) |
1121 | + |
1122 | + def test_create_blank_logo_checksum(self): |
1123 | + request = HttpRequest() |
1124 | + data = {'hostname': 'foo', 'logo_checksum': ''} |
1125 | + request.data = data |
1126 | + request.user = self.user |
1127 | + handler = MachineHandler() |
1128 | + requested_uuid = 'uuid' |
1129 | + |
1130 | + machine = handler.create(request, requested_uuid) |
1131 | + |
1132 | + self.assertEqual(requested_uuid, machine.uuid) |
1133 | + self.assertEqual('', machine.logo_checksum) |
1134 | + |
1135 | + def test_create_updates_existing(self): |
1136 | + machine = self.factory.make_machine(owner=self.user) |
1137 | + handler = MachineHandler() |
1138 | + request = HttpRequest() |
1139 | + request.user = self.user |
1140 | + changed_hostname = machine.hostname + '-changed' |
1141 | + request.data = {'hostname': changed_hostname} |
1142 | + |
1143 | + handler.create(request, machine.uuid) |
1144 | + |
1145 | + updated = Machine.objects.get(uuid=machine.uuid) |
1146 | + self.assertEqual(changed_hostname, updated.hostname) |
1147 | + self.assertEqual('', updated.logo_checksum) |
1148 | + |
1149 | + def test_delete_invalid_uuid_returns_404(self): |
1150 | + handler = MachineHandler() |
1151 | + response = handler.delete(self.request, |
1152 | + self.factory.get_unique_string()) |
1153 | + self.assertEqual(404, response.status_code) |
1154 | + |
1155 | + def test_delete(self): |
1156 | + machine = self.factory.make_machine(owner=self.user) |
1157 | + handler = MachineHandler() |
1158 | + |
1159 | + response = handler.delete(self.request, machine.uuid) |
1160 | + |
1161 | + self.assertEqual(204, response.status_code) |
1162 | + self.assertEqual('', response.content) |
1163 | + self.assertRaises(Machine.DoesNotExist, Machine.objects.get, |
1164 | + uuid=machine.uuid) |
1165 | + |
1166 | + |
1167 | +class PackagesHandlerTestCase(HandlerTestCase): |
1168 | + def test_read_invalid_uuid_returns_404(self): |
1169 | + handler = PackagesHandler() |
1170 | + response = handler.read(self.request, self.factory.get_unique_string()) |
1171 | + self.assertEqual(404, response.status_code) |
1172 | + |
1173 | + def test_read(self): |
1174 | + expected = 'some-package-list' |
1175 | + machine = self.factory.make_machine(owner=self.user, |
1176 | + package_list=expected) |
1177 | + handler = PackagesHandler() |
1178 | + |
1179 | + response = handler.read(self.request, machine.uuid) |
1180 | + self.assertEqual(response, expected) |
1181 | + |
1182 | + def test_create_invalid_uuid_returns_404(self): |
1183 | + handler = PackagesHandler() |
1184 | + response = handler.create(self.request, |
1185 | + self.factory.get_unique_string()) |
1186 | + self.assertEqual(404, response.status_code) |
1187 | + |
1188 | + def test_create_missing_data(self): |
1189 | + machine = self.factory.make_machine(owner=self.user) |
1190 | + request = HttpRequest() |
1191 | + request.user = self.user |
1192 | + data = {'packages_checksum': 'bar', 'package_list': 'some-data'} |
1193 | + handler = PackagesHandler() |
1194 | + for key in data: |
1195 | + request.data = dict((k, data[k]) for k in data if k != key) |
1196 | + response = handler.create(request, machine.uuid) |
1197 | + self.assertEqual('error', response['status']) |
1198 | + self.assertEqual([key], response['errors'].keys()) |
1199 | + |
1200 | + def test_create_success(self): |
1201 | + machine = self.factory.make_machine(owner=self.user) |
1202 | + request = HttpRequest() |
1203 | + request.user = self.user |
1204 | + data = {'packages_checksum': 'bar', 'package_list': 'some-data'} |
1205 | + request.data = data |
1206 | + handler = PackagesHandler() |
1207 | + |
1208 | + response = handler.create(request, machine.uuid) |
1209 | + |
1210 | + updated = Machine.objects.get(uuid=machine.uuid) |
1211 | + self.assertEqual('Success', response) |
1212 | + self.assertEqual(machine.uuid, updated.uuid) |
1213 | + self.assertEqual(data['packages_checksum'], updated.packages_checksum) |
1214 | + self.assertEqual(data['package_list'], updated.package_list) |
1215 | + |
1216 | + def test_create_no_data(self): |
1217 | + """Test the case where no data was deserialized""" |
1218 | + machine = self.factory.make_machine(owner=self.user) |
1219 | + request = HttpRequest() |
1220 | + request.user = self.user |
1221 | + handler = PackagesHandler() |
1222 | + response = handler.create(request, uuid=machine.uuid) |
1223 | + self.assertContains(response, "Unable to deserialize request", |
1224 | + status_code=400) |
Some comments:
l. 115: still using hardcoded User instead of the one from the request
l. 736: factory methods default prefix don't end in '-' (let's be consistent)
l. 845: you don't want to 'import simplejson' directly, but 'from django.utils import simplejson', as that will try to load python's json module first, and fallback to the simplejson module provided by django if not found
l. 911, why not check for status code here? to make sure it's 204?
l. 1003 why test for the content instead of the status code ('Success' vs 200)?. Why is the content not the same as with the status ('Success' vs 'ok')?
l. 1064: why use None for the request when you have a request you can use (self.request)?
Otherwise looks pretty nice.