Merge lp:~joetalbott/uci-engine/add_v2_skeleton into lp:uci-engine

Proposed by Joe Talbott
Status: Rejected
Rejected by: Joe Talbott
Proposed branch: lp:~joetalbott/uci-engine/add_v2_skeleton
Merge into: lp:uci-engine
Diff against target: 826 lines (+252/-70)
7 files modified
ci-utils/ci_utils/tastypie/test.py (+4/-2)
ticket_system/people/api.py (+5/-0)
ticket_system/project/api.py (+10/-0)
ticket_system/ticket/api.py (+91/-0)
ticket_system/ticket/tests/test_read_api.py (+58/-38)
ticket_system/ticket/tests/test_write_api.py (+61/-29)
ticket_system/ticket_system/urls.py (+23/-1)
To merge this branch: bzr merge lp:~joetalbott/uci-engine/add_v2_skeleton
Reviewer Review Type Date Requested Status
Celso Providelo (community) Needs Fixing
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+233410@code.launchpad.net

Commit message

ticket-system - Add v2 API skeleton.

* This will be the authenticated api once those bits are in place.

Description of the change

ticket-system - Add v2 API skeleton.

* This will be the authenticated api once those bits are in place.

To post a comment you must log in.
760. By Joe Talbott

merge with trunk - resolve conflicts

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:760
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1357/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1357/rebuild

review: Needs Fixing (continuous-integration)
761. By Joe Talbott

ticket-system - Fix tests.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:761
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1464/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1464/rebuild

review: Needs Fixing (continuous-integration)
762. By Joe Talbott

merge with trunk (fix conflicts)

763. By Joe Talbott

ticket-system - remove leftover tastypie resources

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:763
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1465/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/1465/rebuild

review: Approve (continuous-integration)
Revision history for this message
Joe Talbott (joetalbott) wrote :

This should be ready to be reviewed now.

Revision history for this message
Celso Providelo (cprov) wrote :

Joe,

Before we dive into this. Why is it necessary to provide a v2 API for supporting authentication/authorization ?

Why can't we right an Authorization class that accommodates our needs [1] and apply that to all existing v1 resources ? Since authorization procedure is pretty homogeneous to all resources.

[1] unrestricted access for requests coming from Lander & GK (bundle.request.META.REMOTE_HOST matching against intercom private-address relations), read-only access to non-private content and write access to logged in users (bundle.request.user.is_authenticated)

review: Needs Fixing
Revision history for this message
Joe Talbott (joetalbott) wrote :

The consensus was that we'd have a v1 api that's unauthenticated and a v2 api that is authenticated. The only major reason I can recall is to handle upgrading a deployment or other clients (CLI for one) that might be in use that aren't able to support authentication yet. This might need to be re-evaluated and would certainly simplify this MP.

Revision history for this message
Celso Providelo (cprov) wrote :

Right, I remember that argument about 'supporting unauthenticated clients', specially CLI, because internal components will probably always be unauthenticated and that's fine from the security PoV and much lighter for the code maintenance/complexity burden PoV.

Regarding the CLI, it would have to be upgraded in order to support authenticated actions either if we block it via central Authorization class or drop v1 from the apache proxy. So, I don't see why we can't land an permissive Authorization class on v1 now and flip the restrictive switch (only authorized writes) when we upgrade the CLI. In both cases, old CLI writes would break when we get *safe*, right ?

Revision history for this message
Joe Talbott (joetalbott) wrote :

On Thu, Sep 25, 2014 at 01:20:15PM -0000, Celso Providelo wrote:
> Right, I remember that argument about 'supporting unauthenticated clients', specially CLI, because internal components will probably always be unauthenticated and that's fine from the security PoV and much lighter for the code maintenance/complexity burden PoV.
>
> Regarding the CLI, it would have to be upgraded in order to support authenticated actions either if we block it via central Authorization class or drop v1 from the apache proxy. So, I don't see why we can't land an permissive Authorization class on v1 now and flip the restrictive switch (only authorized writes) when we upgrade the CLI. In both cases, old CLI writes would break when we get *safe*, right ?

If that's the case we don't need to do anything. All the tastypie
resources are read-only except those that need write access which
already have the default permissive authorization implementation.

So should we just reject this MP?

Revision history for this message
Celso Providelo (cprov) wrote :

I think we should land a custom authorization class to explicitly support:

 1. *local* access for components inside the same deployment, established via juju-relation (think Lander or GK)
 2. *internal* access for components outside the deployment but inside the DC with the appropriate fw rules, established via juju-config (think ci-train jenkins or any other crack-of-the-day subsystem)

External access, coming via webui-apache proxy (host preserved) would be submitted to openid or oauth2 authentication (request.user is setup), at this point, specifically we say 'yes, for now ...' to anonymous request.

Later, when the CLI is able to grab a oauth token and use it, we just patch the custom Authorization class and we are done.

Unmerged revisions

763. By Joe Talbott

ticket-system - remove leftover tastypie resources

762. By Joe Talbott

merge with trunk (fix conflicts)

761. By Joe Talbott

ticket-system - Fix tests.

760. By Joe Talbott

merge with trunk - resolve conflicts

759. By Joe Talbott

ticket-system - Add v2 API skeleton.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ci-utils/ci_utils/tastypie/test.py'
2--- ci-utils/ci_utils/tastypie/test.py 2014-05-27 11:41:04 +0000
3+++ ci-utils/ci_utils/tastypie/test.py 2014-09-25 04:08:48 +0000
4@@ -33,7 +33,8 @@
5 self.client = TestApiClient()
6
7 def _resource(self, resource):
8- if resource[0] == '/' or resource.startswith('http://'):
9+ if (resource != '' and
10+ (resource[0] == '/' or resource.startswith('http://'))):
11 # assume the caller is passing the full path
12 return resource
13 return self.resource_base + '/' + resource
14@@ -41,7 +42,8 @@
15 def get(self, resource, params={}):
16 '''Get the resource and return resource as dict.'''
17 resource = self._resource(resource)
18- return json.loads(self.client.get(resource, data=params).content)
19+ content = self.client.get(resource, data=params).content
20+ return json.loads(content)
21
22 def post(self, resource, params):
23 '''Create the resource and return location of the new object.'''
24
25=== modified file 'ticket_system/people/api.py'
26--- ticket_system/people/api.py 2013-12-12 16:53:14 +0000
27+++ ticket_system/people/api.py 2014-09-25 04:08:48 +0000
28@@ -29,3 +29,8 @@
29 "email": ('exact', 'iexact', 'startswith', 'istartswith'),
30 "is_team": ALL,
31 }
32+
33+
34+class PersonResourceAuth(PersonResource):
35+ class Meta(PersonResource.Meta):
36+ resource_name = 'person'
37
38=== modified file 'ticket_system/project/api.py'
39--- ticket_system/project/api.py 2014-04-16 18:36:03 +0000
40+++ ticket_system/project/api.py 2014-09-25 04:08:48 +0000
41@@ -29,6 +29,11 @@
42 }
43
44
45+class SourcePackageResourceAuth(SourcePackageResource):
46+ class Meta(SourcePackageResource.Meta):
47+ resource_name = 'sourcepackage'
48+
49+
50 class BinaryPackageResource(ModelResource):
51
52 class Meta:
53@@ -38,3 +43,8 @@
54 filtering = {
55 "name": ('exact', 'iexact', 'startswith', 'istartswith'),
56 }
57+
58+
59+class BinaryPackageResourceAuth(BinaryPackageResource):
60+ class Meta(BinaryPackageResource.Meta):
61+ resource_name = 'binarypackage'
62
63=== modified file 'ticket_system/ticket/api.py'
64--- ticket_system/ticket/api.py 2014-09-24 12:32:33 +0000
65+++ ticket_system/ticket/api.py 2014-09-25 04:08:48 +0000
66@@ -166,6 +166,11 @@
67 resource_name = 'spu'
68
69
70+class SourcePackageUploadResourceAuth(SourcePackageUploadResource):
71+ class Meta(SourcePackageUploadResource.Meta):
72+ resource_name = 'spu'
73+
74+
75 class TicketResource(TicketTranslatedResource):
76
77 class Meta:
78@@ -180,6 +185,11 @@
79 paginator_class = PageNumberPaginator
80
81
82+class TicketResourceAuth(TicketResource):
83+ class Meta(TicketResource.Meta):
84+ resource_name = 'ticket'
85+
86+
87 class SubTicketTranslatedResource(ModelResource):
88
89 def dehydrate_current_workflow_step(self, bundle):
90@@ -207,6 +217,11 @@
91 authorization = Authorization()
92
93
94+class SubTicketResourceAuth(SubTicketResource):
95+ class Meta(SubTicketResource.Meta):
96+ resource_name = 'subticket'
97+
98+
99 class TicketArtifactResource(ModelResource):
100 ticket = fields.ToOneField(TicketResource, 'ticket', null=True, full=True)
101
102@@ -220,6 +235,11 @@
103 }
104
105
106+class TicketArtifactResourceAuth(TicketArtifactResource):
107+ class Meta(TicketArtifactResource.Meta):
108+ resource_name = 'ticketartifact'
109+
110+
111 class SubTicketArtifactResource(ModelResource):
112
113 subticket = fields.ToOneField(SubTicketResource, 'subticket',
114@@ -231,6 +251,11 @@
115 authorization = Authorization()
116
117
118+class SubTicketArtifactResourceAuth(SubTicketArtifactResource):
119+ class Meta(SubTicketArtifactResource.Meta):
120+ resource_name = 'subticketartifact'
121+
122+
123 class FullTicketArtifactResource(ModelResource):
124
125 class Meta:
126@@ -238,6 +263,11 @@
127 allowed_methods = ['get']
128
129
130+class FullTicketArtifactResourceAuth(FullTicketArtifactResource):
131+ class Meta(FullTicketArtifactResource.Meta):
132+ resource_name = 'fullticketartifact'
133+
134+
135 class FullSubTicketArtifactResource(ModelResource):
136
137 class Meta:
138@@ -245,6 +275,11 @@
139 allowed_methods = ['get']
140
141
142+class FullSubTicketArtifactResourceAuth(FullSubTicketArtifactResource):
143+ class Meta(FullSubTicketArtifactResource.Meta):
144+ resource_name = 'fullsubticketartifact'
145+
146+
147 class FullSubTicketResource(SubTicketTranslatedResource):
148
149 artifact = fields.ToManyField(FullSubTicketArtifactResource,
150@@ -255,6 +290,11 @@
151 allowed_methods = ['get']
152
153
154+class FullSubTicketResourceAuth(FullSubTicketResource):
155+ class Meta(FullSubTicketResource.Meta):
156+ resource_name = 'fullsubticket'
157+
158+
159 class FullTicketResource(TicketTranslatedResource):
160
161 subticket = fields.ToManyField(FullSubTicketResource, 'subticket_set',
162@@ -268,6 +308,11 @@
163 allowed_methods = ['get']
164
165
166+class FullTicketResourceAuth(FullTicketResource):
167+ class Meta(FullTicketResource.Meta):
168+ resource_name = 'fullticket'
169+
170+
171 class SubTicketUpdateStatusResource(SubTicketTranslatedResource):
172
173 class Meta:
174@@ -281,6 +326,11 @@
175 }
176
177
178+class SubTicketUpdateStatusResourceAuth(SubTicketUpdateStatusResource):
179+ class Meta(SubTicketUpdateStatusResource.Meta):
180+ resource_name = 'updatesubticketstatus'
181+
182+
183 class SubTicketStatusResource(SubTicketTranslatedResource):
184
185 class Meta:
186@@ -312,6 +362,11 @@
187 return self._data
188
189
190+class SubTicketStatusResourceAuth(SubTicketStatusResource):
191+ class Meta(SubTicketStatusResource.Meta):
192+ resource_name = 'subticketstatus'
193+
194+
195 class WorkflowResource(Resource):
196 name = fields.CharField(attribute='name')
197 key = fields.CharField(attribute='key')
198@@ -378,6 +433,11 @@
199 return kwargs
200
201
202+class WorkflowResourceAuth(WorkflowResource):
203+ class Meta(WorkflowResource.Meta):
204+ resource_name = 'workflow'
205+
206+
207 class TicketWorkflowStepResource(WorkflowResource):
208
209 enum_type = ticket_states.TicketWorkflowStep
210@@ -386,6 +446,11 @@
211 resource_name = 'ticket_workflow_step'
212
213
214+class TicketWorkflowStepResourceAuth(TicketWorkflowStepResource):
215+ class Meta(TicketWorkflowStepResource.Meta):
216+ resource_name = 'ticket_workflow_step'
217+
218+
219 class SubTicketWorkflowStepResource(WorkflowResource):
220
221 enum_type = ticket_states.SubTicketWorkflowStep
222@@ -394,6 +459,11 @@
223 resource_name = 'subticket_workflow_step'
224
225
226+class SubTicketWorkflowStepResourceAuth(SubTicketWorkflowStepResource):
227+ class Meta(SubTicketWorkflowStepResource.Meta):
228+ resource_name = 'subticket_workflow_step'
229+
230+
231 class TicketWorkflowStepStatusResource(WorkflowResource):
232
233 enum_type = ticket_states.TicketWorkflowStepStatus
234@@ -402,6 +472,11 @@
235 resource_name = 'ticket_workflow_step_status'
236
237
238+class TicketWorkflowStepStatusResourceAuth(TicketWorkflowStepStatusResource):
239+ class Meta(TicketWorkflowStepStatusResource.Meta):
240+ resource_name = 'ticket_workflow_step_status'
241+
242+
243 class SubTicketWorkflowStepStatusResource(WorkflowResource):
244
245 enum_type = ticket_states.SubTicketWorkflowStepStatus
246@@ -410,6 +485,12 @@
247 resource_name = 'subticket_workflow_step_status'
248
249
250+class SubTicketWorkflowStepStatusResourceAuth(
251+ SubTicketWorkflowStepStatusResource):
252+ class Meta(SubTicketWorkflowStepStatusResource.Meta):
253+ resource_name = 'subticket_workflow_step_status'
254+
255+
256 class ReviewResource(ModelResource):
257 ticket = fields.ForeignKey(TicketResource, 'ticket')
258
259@@ -423,6 +504,11 @@
260 }
261
262
263+class ReviewResourceAuth(ReviewResource):
264+ class Meta(ReviewResource.Meta):
265+ resource_name = 'review'
266+
267+
268 class TSStatus(ModelResource):
269 class Meta():
270 resource_name = 'status'
271@@ -471,3 +557,8 @@
272 ticket_uuid = resp['location'].split('/')[-2]
273 Ticket.objects.get(uuid=ticket_uuid).delete()
274 return True
275+
276+
277+class TSStatusAuth(TSStatus):
278+ class Meta(TSStatus.Meta):
279+ resource_name = 'status'
280
281=== modified file 'ticket_system/ticket/tests/test_read_api.py'
282--- ticket_system/ticket/tests/test_read_api.py 2014-09-19 15:03:57 +0000
283+++ ticket_system/ticket/tests/test_read_api.py 2014-09-25 04:08:48 +0000
284@@ -39,27 +39,41 @@
285 class TicketLookupTest(TicketTastypieTestCase):
286
287 def setUp(self):
288- super(TicketLookupTest, self).setUp('/api/v1')
289+ self.api_version = 'v1'
290+ super(TicketLookupTest, self).setUp(
291+ '/api/{}'.format(self.api_version))
292 self.ticket = mommy.make('Ticket')
293
294 def test_by_id(self):
295 # A `Ticket` can be retrieved using its ID (pk, integer).
296- obj = self.getResource('ticket/{}/'.format(self.ticket.pk))
297+ obj = self.getResource('ticket/{}/'.format(self.ticket.id))
298 self.assertEqual(self.ticket.pk, obj['id'])
299+ self.assertIn(self.api_version, obj['resource_uri'])
300+
301+ def test_lookup_by_id_v2(self):
302+ # A `Ticket` can be retrieved using its ID (pk, integer).
303+ self.api_version = 'v2'
304+ obj = self.getResource('/api/{}/ticket/{}/'.format(self.api_version,
305+ self.ticket.id))
306+ self.assertEqual(self.ticket.id, obj['id'])
307+ self.assertIn(self.api_version, obj['resource_uri'])
308
309 def test_by_uuid(self):
310 # A `Ticket` can be also retrieved by its UUID (uuid, UUIDv1).
311 obj = self.getResource('ticket/{}/'.format(self.ticket.uuid))
312 self.assertEqual(self.ticket.pk, obj['id'])
313+ self.assertIn(self.api_version, obj['resource_uri'])
314
315 def test_404(self):
316 # Failed `Ticket` lookups return an 404 with no contents.
317 # Unknown ID.
318- response = self.client.get('/api/v1/ticket/1000/')
319+ response = self.client.get(
320+ '/api/{}/ticket/1000/'.format(self.api_version))
321 self.assertEqual(404, response.status_code)
322 self.assertEqual('', response.content)
323 # Unknown UUID
324- response = self.client.get('/api/v1/ticket/{}/'.format(uuid.uuid1()))
325+ response = self.client.get('/api/{}/ticket/{}/'.format(
326+ self.api_version, uuid.uuid1()))
327 self.assertEqual(404, response.status_code)
328 self.assertEqual('', response.content)
329
330@@ -92,7 +106,9 @@
331 class TicketReadAPITest(TicketTastypieTestCase):
332
333 def setUp(self):
334- super(TicketReadAPITest, self).setUp('/api/v1')
335+ self.api_version = 'v1'
336+ super(TicketReadAPITest, self).setUp(
337+ '/api/{}'.format(self.api_version))
338 self.ticket = mommy.make('Ticket')
339 self.sourcepackage = sourcepackage_recipe.make()
340 self.spu = mommy.make('SourcePackageUpload',
341@@ -120,13 +136,14 @@
342 u'architecture': unicode(self.spu.architecture),
343 u'version': unicode(self.spu.version),
344 u'sourcepackage': {
345- u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
346- self.sourcepackage.pk),
347+ u'resource_uri': u'/api/{1}/sourcepackage/{0}/'.format(
348+ self.sourcepackage.pk, self.api_version),
349 u'id': self.sourcepackage.pk,
350 u'name': unicode(self.sourcepackage.name)},
351 u'id': self.spu.pk,
352 u'resource_uri':
353- u'/api/v1/spu/{0}/'.format(self.spu.pk)},
354+ u'/api/{1}/spu/{0}/'.format(
355+ self.spu.pk, self.api_version)},
356 u'ticket': {
357 u'status': unicode(get_enum_title(self.ticket.status,
358 TicketWorkflowStepStatus)),
359@@ -145,8 +162,8 @@
360 u'lander_unit': 0,
361 u'master_ppa': unicode(self.ticket.master_ppa),
362 u'build_ppa': unicode(self.ticket.build_ppa),
363- u'resource_uri': u'/api/v1/ticket/{0}/'.format(
364- self.ticket.uuid),
365+ u'resource_uri': u'/api/{1}/ticket/{0}/'.format(
366+ self.ticket.uuid, self.api_version),
367 u'series': unicode(self.ticket.series),
368 u'private': False,
369 u'updated': unicode(self.ticket.updated.strftime(
370@@ -154,12 +171,12 @@
371 u'uuid': self.ticket.uuid},
372 u'id': self.subticket.pk,
373 u'conflicts': [],
374- u'resource_uri': u'/api/v1/subticket/{0}/'.format(
375- self.subticket.pk)},
376+ u'resource_uri': u'/api/{1}/subticket/{0}/'.format(
377+ self.subticket.pk, self.api_version)},
378 u'type': unicode(self.artifact_1.type),
379 u'id': self.artifact_1.pk,
380- u'resource_uri': u'/api/v1/subticketartifact/{0}/'.format(
381- self.artifact_1.pk),
382+ u'resource_uri': u'/api/{1}/subticketartifact/{0}/'.format(
383+ self.artifact_1.pk, self.api_version),
384 })
385
386 def test_get_artifact_api_ticket(self):
387@@ -185,8 +202,8 @@
388 u'lander_unit': 0,
389 u'master_ppa': unicode(self.ticket.master_ppa),
390 u'build_ppa': unicode(self.ticket.build_ppa),
391- u'resource_uri': u'/api/v1/ticket/{0}/'.format(
392- self.ticket.uuid),
393+ u'resource_uri': u'/api/{1}/ticket/{0}/'.format(
394+ self.ticket.uuid, self.api_version),
395 u'series': unicode(self.ticket.series),
396 u'private': False,
397 u'updated': unicode(self.ticket.updated.strftime(
398@@ -194,8 +211,8 @@
399 u'uuid': self.ticket.uuid},
400 u'type': unicode(self.artifact_2.type),
401 u'id': self.artifact_2.pk,
402- u'resource_uri': u'/api/v1/ticketartifact/{0}/'.format(
403- self.artifact_2.pk),
404+ u'resource_uri': u'/api/{1}/ticketartifact/{0}/'.format(
405+ self.artifact_2.pk, self.api_version),
406 })
407
408 def test_get_subticket_api(self):
409@@ -206,19 +223,19 @@
410 self.subticket.current_workflow_step,
411 SubTicketWorkflowStep)),
412 u'id': self.subticket.pk,
413- u'resource_uri': u'/api/v1/subticket/{0}/'.format(
414- self.subticket.pk),
415+ u'resource_uri': u'/api/{1}/subticket/{0}/'.format(
416+ self.subticket.pk, self.api_version),
417 u'source_package_upload': {
418 u'architecture': unicode(self.spu.architecture),
419 u'version': unicode(self.spu.version),
420 u'sourcepackage': {
421- u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
422- self.sourcepackage.pk),
423+ u'resource_uri': u'/api/{1}/sourcepackage/{0}/'.format(
424+ self.sourcepackage.pk, self.api_version),
425 u'id': self.sourcepackage.pk,
426 u'name': unicode(self.sourcepackage.name)},
427 u'id': self.spu.pk,
428 u'resource_uri':
429- u'/api/v1/spu/{0}/'.format(self.spu.pk)},
430+ u'/api/{1}/spu/{0}/'.format(self.spu.pk, self.api_version)},
431 u'status': unicode(get_enum_title(self.subticket.status,
432 SubTicketWorkflowStepStatus)),
433 u'ticket': {
434@@ -239,8 +256,8 @@
435 u'status': unicode(get_enum_title(self.ticket.status,
436 TicketWorkflowStepStatus)),
437 u'title': unicode(self.ticket.title),
438- u'resource_uri': u'/api/v1/ticket/{0}/'.format(
439- self.ticket.uuid),
440+ u'resource_uri': u'/api/{1}/ticket/{0}/'.format(
441+ self.ticket.uuid, self.api_version),
442 u'series': unicode(self.ticket.series),
443 u'private': False,
444 u'updated': unicode(self.ticket.updated.strftime(
445@@ -295,8 +312,8 @@
446 u'lander_unit': 0,
447 u'master_ppa': unicode(self.ticket.master_ppa),
448 u'build_ppa': unicode(self.ticket.build_ppa),
449- u'resource_uri': u'/api/v1/ticket/{0}/'.format(
450- self.ticket.uuid),
451+ u'resource_uri': u'/api/{1}/ticket/{0}/'.format(
452+ self.ticket.uuid, self.api_version),
453 u'series': unicode(self.ticket.series),
454 u'updated': unicode(self.ticket.updated.strftime(
455 settings.TEST_DATETIME_FORMAT)),
456@@ -343,21 +360,21 @@
457 u'architecture': unicode(self.spu.architecture),
458 u'version': unicode(self.spu.version),
459 u'sourcepackage': {
460- u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
461- self.sourcepackage.pk),
462+ u'resource_uri': u'/api/{1}/sourcepackage/{0}/'.format(
463+ self.sourcepackage.pk, self.api_version),
464 u'id': self.sourcepackage.pk,
465 u'name': unicode(self.sourcepackage.name)},
466 u'id': self.spu.pk,
467 u'resource_uri':
468- u'/api/v1/spu/{0}/'.format(self.spu.pk),
469+ u'/api/{1}/spu/{0}/'.format(self.spu.pk, self.api_version),
470 })
471
472 def test_get_subticket_update_status_api(self):
473 obj = self.getResource('updatesubticketstatus/')
474 self.assertEqual(obj['objects'][0], {
475 u'id': self.subticket.pk,
476- u'resource_uri': u'/api/v1/updatesubticketstatus/{0}/'.format(
477- self.subticket.pk),
478+ u'resource_uri': u'/api/{1}/updatesubticketstatus/{0}/'.format(
479+ self.subticket.pk, self.api_version),
480 u'current_workflow_step': unicode(get_enum_title(
481 self.subticket.current_workflow_step,
482 SubTicketWorkflowStep)),
483@@ -370,8 +387,8 @@
484 'updatesubticketstatus/?id={}'.format(self.subticket.pk))
485 self.assertEqual(obj['objects'][0], {
486 u'id': self.subticket.pk,
487- u'resource_uri': u'/api/v1/updatesubticketstatus/{0}/'.format(
488- self.subticket.pk),
489+ u'resource_uri': u'/api/{1}/updatesubticketstatus/{0}/'.format(
490+ self.subticket.pk, self.api_version),
491 u'current_workflow_step': unicode(get_enum_title(
492 self.subticket.current_workflow_step,
493 SubTicketWorkflowStep)),
494@@ -383,8 +400,8 @@
495 obj = self.getResource('subticketstatus/')
496 self.assertEqual(obj['objects'][0], {
497 u'id': self.subticket.pk,
498- u'resource_uri': u'/api/v1/subticketstatus/{0}/'.format(
499- self.subticket.pk),
500+ u'resource_uri': u'/api/{1}/subticketstatus/{0}/'.format(
501+ self.subticket.pk, self.api_version),
502 u'current_workflow_step': unicode(get_enum_title(
503 self.subticket.current_workflow_step,
504 SubTicketWorkflowStep)),
505@@ -416,7 +433,8 @@
506 obj = self.getResource(uri)
507 orig = obj['objects'][0]
508 resource_uri = obj['objects'][0]['resource_uri']
509- item_uri = resource_uri.replace('/api/v1/', '')
510+ item_uri = resource_uri.replace('/api/{}/'.format(self.api_version),
511+ '')
512
513 obj = self.getResource(item_uri)
514 self.assertEqual(orig, obj)
515@@ -438,7 +456,9 @@
516
517 @mock.patch('ci_utils.amqp_utils.send')
518 def setUp(self, send):
519- super(TicketPaginationTest, self).setUp('/api/v1')
520+ self.api_version = 'v1'
521+ super(TicketPaginationTest, self).setUp(
522+ '/api/{}'.format(self.api_version))
523 for i in range(5):
524 mommy.make('Ticket', title='ticket-{}'.format(i + 1))
525
526
527=== modified file 'ticket_system/ticket/tests/test_write_api.py'
528--- ticket_system/ticket/tests/test_write_api.py 2014-08-19 20:34:04 +0000
529+++ ticket_system/ticket/tests/test_write_api.py 2014-09-25 04:08:48 +0000
530@@ -55,7 +55,9 @@
531 class APICreateTicketResourceTest(TicketTastypieTestCase):
532
533 def setUp(self):
534- super(APICreateTicketResourceTest, self).setUp('/api/v1')
535+ self.api_version = 'v1'
536+ super(APICreateTicketResourceTest, self).setUp(
537+ '/api/{}'.format(self.api_version))
538 self.ticket = create_ticket()
539 self.resource = 'ticket/'
540 self.detail_url = self.resource + '{0}/'.format(self.ticket.pk)
541@@ -97,7 +99,8 @@
542 def test_patch_detail(self):
543 # `Ticket` object can be patched via the API.
544 new_data = {'owner': 'ci@example.com'}
545- resp = self.client.patch('/api/v1/' + self.detail_url, data=new_data)
546+ resp = self.client.patch('/api/{}/{}'.format(
547+ self.api_version, self.detail_url), data=new_data)
548 self.assertHttpAccepted(resp)
549 self.assertEqual(
550 new_data['owner'], Ticket.objects.get(pk=self.ticket.pk).owner)
551@@ -112,14 +115,16 @@
552 class APICreateSPUResourceTest(TicketTastypieTestCase):
553
554 def setUp(self):
555- super(APICreateSPUResourceTest, self).setUp('/api/v1')
556+ self.api_version = 'v1'
557+ super(APICreateSPUResourceTest, self).setUp(
558+ '/api/{}'.format(self.api_version))
559 self.sourcepackage = sourcepackage_recipe.make()
560 self.spu = mommy.make('SourcePackageUpload',
561 sourcepackage=self.sourcepackage)
562 self.resource = 'spu/'
563 self.detail_url = self.resource + '{0}/'.format(self.spu.pk)
564- self.sourcepackage_uri = '/api/v1/sourcepackage/{0}/'.format(
565- self.sourcepackage.pk)
566+ self.sourcepackage_uri = '/api/{1}/sourcepackage/{0}/'.format(
567+ self.sourcepackage.pk, self.api_version)
568 self.post_spu_data = {
569 'sourcepackage': self.sourcepackage_uri,
570 'version': '1.0',
571@@ -139,7 +144,8 @@
572 new_data['version'] = '2.0'
573
574 self.assertEqual(SourcePackageUpload.objects.count(), 1)
575- resp = self.client.patch('/api/v1/' + self.detail_url, data=new_data)
576+ resp = self.client.patch('/api/{1}/{0}'.format(
577+ self.detail_url, self.api_version), data=new_data)
578 self.assertHttpMethodNotAllowed(resp)
579 # Make sure the count hasn't changed & we did an update.
580 self.assertEqual(SourcePackageUpload.objects.count(), 1)
581@@ -155,7 +161,9 @@
582 class APICreateSubTicketResourceTest(TicketTastypieTestCase):
583
584 def setUp(self):
585- super(APICreateSubTicketResourceTest, self).setUp('/api/v1')
586+ self.api_version = 'v1'
587+ super(APICreateSubTicketResourceTest, self).setUp(
588+ '/api/{}'.format(self.api_version))
589 self.ticket = mommy.make('Ticket')
590 self.sourcepackage = sourcepackage_recipe.make()
591 self.spu = mommy.make('SourcePackageUpload',
592@@ -164,8 +172,10 @@
593 source_package_upload=self.spu)
594 self.resource = 'subticket/'
595 self.detail_url = self.resource + '{0}/'.format(self.subticket.pk)
596- self.ticket_uri = '/api/v1/ticket/{0}/'.format(self.ticket.pk)
597- self.spu_uri = '/api/v1/spu/{0}/'.format(self.spu.pk)
598+ self.ticket_uri = '/api/{1}/ticket/{0}/'.format(
599+ self.ticket.pk, self.api_version)
600+ self.spu_uri = '/api/{1}/spu/{0}/'.format(
601+ self.spu.pk, self.api_version)
602 self.post_subticket_data = {
603 'source_package_upload': self.spu_uri,
604 'ticket': self.ticket_uri,
605@@ -186,7 +196,8 @@
606 new_data['assignee'] = 'me@example.com'
607
608 self.assertEqual(SubTicket.objects.count(), 1)
609- resp = self.client.patch('/api/v1/' + self.detail_url, data=new_data)
610+ resp = self.client.patch('/api/{1}/{0}'.format(
611+ self.detail_url, self.api_version), data=new_data)
612 self.assertHttpMethodNotAllowed(resp)
613 # Make sure the count hasn't changed & we did an update.
614 self.assertEqual(SubTicket.objects.count(), 1)
615@@ -202,12 +213,15 @@
616 class APICreateTicketArtifactResourceTest(TicketTastypieTestCase):
617
618 def setUp(self):
619- super(APICreateTicketArtifactResourceTest, self).setUp('/api/v1')
620+ self.api_version = 'v1'
621+ super(APICreateTicketArtifactResourceTest, self).setUp(
622+ '/api/{}'.format(self.api_version))
623 self.ticket = mommy.make('Ticket')
624 self.artifact = mommy.make('TicketArtifact', ticket=self.ticket)
625 self.resource = 'ticketartifact/'
626 self.detail_url = self.resource + '{0}/'.format(self.artifact.pk)
627- self.ticket_uri = '/api/v1/ticket/{0}/'.format(self.ticket.pk)
628+ self.ticket_uri = '/api/{1}/ticket/{0}/'.format(
629+ self.ticket.pk, self.api_version)
630 self.post_artifact_data_ticket = {
631 'type': 'LOGS',
632 'name': 'my_artifact',
633@@ -233,7 +247,8 @@
634 new_data['name'] = 'my_upload'
635
636 self.assertEqual(TicketArtifact.objects.count(), 1)
637- resp = self.client.patch('/api/v1/' + self.detail_url, data=new_data)
638+ resp = self.client.patch('/api/{1}/{0}'.format(
639+ self.detail_url, self.api_version), data=new_data)
640 self.assertHttpMethodNotAllowed(resp)
641 # Make sure the count hasn't changed & we did an update.
642 self.assertEqual(TicketArtifact.objects.count(), 1)
643@@ -249,7 +264,9 @@
644 class APICreateSubTicketArtifactResourceTest(TicketTastypieTestCase):
645
646 def setUp(self):
647- super(APICreateSubTicketArtifactResourceTest, self).setUp('/api/v1')
648+ self.api_version = 'v1'
649+ super(APICreateSubTicketArtifactResourceTest, self).setUp(
650+ '/api/{}'.format(self.api_version))
651 self.sourcepackage = sourcepackage_recipe.make()
652 self.spu = mommy.make('SourcePackageUpload',
653 sourcepackage=self.sourcepackage)
654@@ -260,7 +277,8 @@
655 subticket=self.subticket)
656 self.resource = 'subticketartifact/'
657 self.detail_url = self.resource + '{0}/'.format(self.artifact.pk)
658- self.subticket_uri = '/api/v1/subticket/{0}/'.format(self.subticket.pk)
659+ self.subticket_uri = '/api/{1}/subticket/{0}/'.format(
660+ self.subticket.pk, self.api_version)
661 self.post_artifact_data_subticket = {
662 'type': 'SPU',
663 'name': 'my_artifact',
664@@ -286,7 +304,8 @@
665 new_data['name'] = 'my_upload'
666
667 self.assertEqual(SubTicketArtifact.objects.count(), 1)
668- resp = self.client.patch('/api/v1/' + self.detail_url, data=new_data)
669+ resp = self.client.patch('/api/{1}/{0}'.format(
670+ self.detail_url, self.api_version), data=new_data)
671 self.assertHttpMethodNotAllowed(resp)
672 # Make sure the count hasn't changed & we did an update.
673 self.assertEqual(SubTicketArtifact.objects.count(), 1)
674@@ -298,7 +317,9 @@
675 class APIUpdateTicketResourceTest(TicketTastypieTestCase):
676
677 def setUp(self):
678- super(APIUpdateTicketResourceTest, self).setUp('/api/v1')
679+ self.api_version = 'v1'
680+ super(APIUpdateTicketResourceTest, self).setUp(
681+ '/api/{}'.format(self.api_version))
682 self.ticket = create_ticket()
683 self.ticket_detail_url = 'ticket/{0}/'.format(self.ticket.pk)
684
685@@ -310,7 +331,8 @@
686 TicketWorkflowStepStatus.COMPLETED.value),
687 }
688 self.assertEqual(Ticket.objects.count(), 1)
689- self.patch('/api/v1/' + self.ticket_detail_url, new_data)
690+ self.patch('/api/{1}/{0}'.format(
691+ self.ticket_detail_url, self.api_version), new_data)
692 # Make sure the count hasn't changed & we did an update.
693 self.assertEqual(Ticket.objects.count(), 1)
694 # Check for updated data.
695@@ -323,7 +345,8 @@
696
697 def test_patch_lander_unit(self):
698 new_data = {'lander_unit': 123}
699- self.patch('/api/v1/' + self.ticket_detail_url, new_data)
700+ self.patch('/api/{1}/{0}'.format(
701+ self.ticket_detail_url, self.api_version), new_data)
702 unit = Ticket.objects.get(pk=self.ticket.pk).lander_unit
703 self.assertEqual(new_data['lander_unit'], unit)
704
705@@ -339,7 +362,8 @@
706 self.assertEqual(Ticket.objects.get(
707 pk=self.ticket.pk).status,
708 self.ticket.status)
709- self.patch('/api/v1/' + self.ticket_detail_url, new_data)
710+ self.patch('/api/{1}/{0}'.format(
711+ self.ticket_detail_url, self.api_version), new_data)
712 self.assertEqual(Ticket.objects.count(), 1)
713 self.assertEqual(Ticket.objects.get(
714 pk=self.ticket.pk).current_workflow_step,
715@@ -366,7 +390,8 @@
716 self.assertEqual(Ticket.objects.get(
717 pk=self.ticket.pk).status,
718 self.ticket.status)
719- self.patch('/api/v1/' + self.ticket_detail_url, new_data)
720+ self.patch('/api/{1}/{0}'.format(
721+ self.ticket_detail_url, self.api_version), new_data)
722 self.assertEqual(Ticket.objects.count(), 1)
723 self.assertEqual(Ticket.objects.get(
724 pk=self.ticket.pk).current_workflow_step,
725@@ -390,21 +415,24 @@
726 self.assertEqual(Ticket.objects.get(
727 pk=self.ticket.pk).status,
728 self.ticket.status)
729- resp = self.client.patch('/api/v1/ticket/17394/', data=new_data)
730+ resp = self.client.patch(
731+ '/api/{}/ticket/17394/'.format(self.api_version), data=new_data)
732 self.assertEqual(Ticket.objects.count(), 1)
733 self.assertEqual(resp.status_code, 404)
734
735 def test_update_ticket_just_status(self):
736 '''Ensure we can update just the status'''
737 new_data = {"status": TicketWorkflowStepStatus.INPROGRESS.value}
738- self.patch('/api/v1/' + self.ticket_detail_url, new_data)
739+ self.patch('/api/{1}/{0}'.format(
740+ self.ticket_detail_url, self.api_version), new_data)
741 status = Ticket.objects.get(pk=self.ticket.pk).status
742 self.assertEqual(new_data['status'], status)
743
744 def test_update_ticket_string(self):
745 '''Ensure string status updates work'''
746 new_data = {"status": TicketWorkflowStepStatus.INPROGRESS.title}
747- self.patch('/api/v1/' + self.ticket_detail_url, new_data)
748+ self.patch('/api/{1}/{0}'.format(
749+ self.ticket_detail_url, self.api_version), new_data)
750 status = Ticket.objects.get(pk=self.ticket.pk).status
751 self.assertEqual(TicketWorkflowStepStatus.INPROGRESS.value, status)
752
753@@ -422,7 +450,9 @@
754 class APIUpdateSubTicketStatuses(TicketTastypieTestCase):
755
756 def setUp(self):
757- super(APIUpdateSubTicketStatuses, self).setUp('/api/v1')
758+ self.api_version = 'v1'
759+ super(APIUpdateSubTicketStatuses, self).setUp(
760+ '/api/{}'.format(self.api_version))
761 self.sourcepackage = sourcepackage_recipe.make()
762 self.spu = mommy.make('SourcePackageUpload',
763 sourcepackage=self.sourcepackage)
764@@ -438,8 +468,8 @@
765 }
766
767 self.assertEqual(SubTicket.objects.count(), 1)
768- self.patch('/api/v1/' + self.subticket_detail_url,
769- new_data)
770+ self.patch('/api/{1}/{0}'.format(
771+ self.subticket_detail_url, self.api_version), new_data)
772 # Make sure the count hasn't changed & we did an update.
773 self.assertEqual(SubTicket.objects.count(), 1)
774 # Check for updated data.
775@@ -456,9 +486,11 @@
776
777 class ReviewTestCase(TicketTastypieTestCase):
778 def setUp(self):
779- super(ReviewTestCase, self).setUp('/api/v1')
780+ self.api_version = 'v1'
781+ super(ReviewTestCase, self).setUp('/api/{}'.format(self.api_version))
782 self.ticket = create_ticket()
783- self.ticket_uri = '/api/v1/ticket/' + str(self.ticket.pk) + '/'
784+ self.ticket_uri = '/api/{1}/ticket/{0}/'.format(
785+ str(self.ticket.pk), self.api_version)
786
787 def test_create(self):
788 params = {
789
790=== modified file 'ticket_system/ticket_system/urls.py'
791--- ticket_system/ticket_system/urls.py 2014-08-29 18:28:56 +0000
792+++ ticket_system/ticket_system/urls.py 2014-09-25 04:08:48 +0000
793@@ -49,10 +49,32 @@
794 v1_api.register(ticket_api.ReviewResource())
795 v1_api.register(ticket_api.TSStatus())
796
797+v2_api = Api(api_name='v2')
798+v2_api.register(people_api.PersonResourceAuth())
799+v2_api.register(project_api.SourcePackageResourceAuth())
800+v2_api.register(project_api.BinaryPackageResourceAuth())
801+v2_api.register(ticket_api.TicketResourceAuth())
802+v2_api.register(ticket_api.SubTicketResourceAuth())
803+v2_api.register(ticket_api.SourcePackageUploadResourceAuth())
804+v2_api.register(ticket_api.TicketArtifactResourceAuth())
805+v2_api.register(ticket_api.SubTicketArtifactResourceAuth())
806+v2_api.register(ticket_api.FullTicketArtifactResourceAuth())
807+v2_api.register(ticket_api.FullSubTicketArtifactResourceAuth())
808+v2_api.register(ticket_api.FullSubTicketResourceAuth())
809+v2_api.register(ticket_api.FullTicketResourceAuth())
810+v2_api.register(ticket_api.SubTicketUpdateStatusResourceAuth())
811+v2_api.register(ticket_api.SubTicketStatusResourceAuth())
812+v2_api.register(ticket_api.TicketWorkflowStepResourceAuth())
813+v2_api.register(ticket_api.SubTicketWorkflowStepResourceAuth())
814+v2_api.register(ticket_api.TicketWorkflowStepStatusResourceAuth())
815+v2_api.register(ticket_api.SubTicketWorkflowStepStatusResourceAuth())
816+v2_api.register(ticket_api.ReviewResourceAuth())
817+v2_api.register(ticket_api.TSStatusAuth())
818
819 urlpatterns = patterns(
820 '',
821- (r'^api/', include(v1_api.urls)),
822+ url(r'^api/', include(v1_api.urls)),
823+ url(r'^api/', include(v2_api.urls)),
824 url(r'^admin/', include(admin.site.urls)),
825 url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')),
826 url(r'^openid/', include('django_openid_auth.urls')),

Subscribers

People subscribed via source and target branches