Merge lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-read-api into lp:ubuntu-ci-services-itself

Proposed by Chris Johnston
Status: Merged
Approved by: Chris Johnston
Approved revision: 38
Merged at revision: 41
Proposed branch: lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-read-api
Merge into: lp:ubuntu-ci-services-itself
Diff against target: 513 lines (+417/-4)
8 files modified
docs/components/ticket-system.rst (+7/-0)
ticket_system/ticket/api.py (+89/-0)
ticket_system/ticket/migrations/0001_initial.py (+2/-2)
ticket_system/ticket/models.py (+2/-2)
ticket_system/ticket/tests/__init__.py (+18/-0)
ticket_system/ticket/tests/test_full_read_api.py (+126/-0)
ticket_system/ticket/tests/test_read_api.py (+163/-0)
ticket_system/ticket_system/urls.py (+10/-0)
To merge this branch: bzr merge lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-read-api
Reviewer Review Type Date Requested Status
Ursula Junque (community) Approve
Francis Ginther Approve
Review via email: mp+198870@code.launchpad.net

Commit message

Add read API to tickets

To post a comment you must log in.
31. By Andy Doan

[r=Francis Ginther] Disable the requirement for ApiKey authentication for TastyPie componenents.

For the first phase of this project we are going to disable authentication. By doing this, we get the added bonus of not having to lug around the ugly "USE_TZ=True" hack. from Andy Doan

32. By Andy Doan

[r=Evan Dandrea] make the ticket-system deployable via django charm from Andy Doan

33. By Chris Johnston

[r=Francis Ginther, Andy Doan] Add a write API for adding people, update docs with API examples from Chris Johnston

34. By Andy Doan

[r=Francis Ginther] Disable the requirement for ApiKey authentication for TastyPie componenents.

For the first phase of this project we are going to disable authentication. By doing this, we get the added bonus of not having to lug around the ugly "USE_TZ=True" hack. from Andy Doan

35. By Andy Doan

[r=Francis Ginther] this moves some utility code out of the branch-source-builder and into ci-utils for others to use. from Andy Doan

36. By Andy Doan

[r=Vincent Ladeuil] configure a default LP user for the ppa-assigner in the juju deployer

also fixes a typo/bug for the dependencies
  from Andy Doan

37. By Andy Doan

[r=Vincent Ladeuil] add documentation on how to set up an oauth token from Andy Doan

38. By Chris Johnston

Fix conflict

Revision history for this message
Ursula Junque (ursinha) wrote :

Hi Chris,

I have one question:

Other components will need to reach subtickets directly to update their status, for example the Lander needs to reach the subticket to mark it as built. AFAIK all other components will use the Ticket ID to reach it, so we'd need to do something like api/v1/ticket/foobar/123, 'foobar' being the source package name and 123 the ticket ID. Is that possible with tastypie? If not, we could do something like 'foobar-123' and parse it. This way you wouldn't need to expose subticket directly, as it would be only used internally, considering the fullticket displays all subtickets and for WebUI purposes that should be enough.

Of course, this approach works considering there will be only one source package upload in active state per source package in a ticket -- others can correct me if I'm wrong and we should consider several landings of the same SP in a single ticket.

Cheers,

Revision history for this message
Chris Johnston (cjohnston) wrote :

I believe what your referring to here can be accomplished by nested
resources from tastypie, however IMO it is outside the scope of this MP.
This MP is purely for consuming data from the ticket system, mainly for the
web UI. Any other concerns with this MP?

Revision history for this message
Francis Ginther (fginther) wrote :

For the initial phase, the lander and other internal components will not be sending updates via the REST API. The only consumers for the this API should be the front end web ui.

review: Approve
Revision history for this message
Chris Johnston (cjohnston) wrote :

Francis, how will the ticket statuses be kept up to date?

Revision history for this message
Francis Ginther (fginther) wrote :

> Francis, how will the ticket statuses be kept up to date?

The original design had the ticket system polling the lander for progress. This was updated to use a message queue instead. The ticket system provides a message queue handle to the lander as part of the execute_request call. The lander then uses this to provide regular progress updates (every 60 seconds or sooner if status changes). The exact details of what is in the message haven't been designed yet, but it would probably have to provide the status of all lander stages in each message. For example:

{"Package Building": "Completed",
 "Image Building": "In Progress",
 "Image Testing": "Not Started",
 "Package Publishing": "Not Started"}

Revision history for this message
Chris Johnston (cjohnston) wrote :

Ok, so the TS will be polling the message queue looking for status changes?

Revision history for this message
Ursula Junque (ursinha) wrote :

> Ok, so the TS will be polling the message queue looking for status changes?

That's my understanding, confirmed by fginther:
<Ursinha> fginther, let me see if I got the message queue idea: "Lander" updates whatever it is in the message queue, ticket system polls the message queue for updates, therefore there's no need of a write API to update ticket status for phase 0?
<fginther> Ursinha, yes, you're correct

(slightly edited for simplicity :))

Revision history for this message
Andy Doan (doanac) wrote :

On Mon, Dec 16, 2013 at 7:31 PM, Ursula Junque <email address hidden> wrote:

> That's my understanding, confirmed by fginther:
> <Ursinha> fginther, let me see if I got the message queue idea: "Lander"
> updates whatever it is in the message queue, ticket system polls the
> message queue for updates, therefore there's no need of a write API to
> update ticket status for phase 0?
> <fginther> Ursinha, yes, you're correct
>

We have a "rabbit worker" example that you can steal from to get started:

http://bazaar.launchpad.net/~canonical-ci-engineering/ubuntu-ci-services-itself/trunk/view/head:/branch-source-builder/run_worker

Its juju deployable with:

http://bazaar.launchpad.net/~canonical-ci-engineering/ubuntu-ci-services-itself/trunk/view/head:/juju-deployer/branch-source-builder.yaml#L17

I'm guessing your "on_message" calls would basically be updates to the
django model or something

Revision history for this message
Ursula Junque (ursinha) wrote :

For phase 0 I think this is okay, I'll make the necessary changes for subticket when we need it later.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/components/ticket-system.rst'
2--- docs/components/ticket-system.rst 2013-12-12 16:53:14 +0000
3+++ docs/components/ticket-system.rst 2013-12-16 14:22:25 +0000
4@@ -137,6 +137,9 @@
5
6 List all open tickets.
7
8+::
9+
10+ curl --dump-header - http://162.213.34.2:8000/api/v1/ticket/
11
12 create_ticket
13 ~~~~~~~~~~~~~
14@@ -148,6 +151,10 @@
15
16 Return the ticket given its id.
17
18+::
19+
20+ curl --dump-header - http://162.213.34.2:8000/api/v1/ticket/1/
21+
22 get_ticket_status
23 ~~~~~~~~~~~~~~~~~
24
25
26=== added file 'ticket_system/ticket/api.py'
27--- ticket_system/ticket/api.py 1970-01-01 00:00:00 +0000
28+++ ticket_system/ticket/api.py 2013-12-16 14:22:25 +0000
29@@ -0,0 +1,89 @@
30+# Ubuntu Continuous Integration Engine
31+# Copyright 2013 Canonical Ltd.
32+
33+# This program is free software: you can redistribute it and/or modify it
34+# under the terms of the GNU Affero General Public License version 3, as
35+# published by the Free Software Foundation.
36+
37+# This program is distributed in the hope that it will be useful, but
38+# WITHOUT ANY WARRANTY; without even the implied warranties of
39+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
40+# PURPOSE. See the GNU Affero General Public License for more details.
41+
42+# You should have received a copy of the GNU Affero General Public License
43+# along with this program. If not, see <http://www.gnu.org/licenses/>.
44+
45+from tastypie import fields
46+from tastypie.resources import ModelResource
47+from ticket.models import Ticket, SubTicket, SourcePackageUpload, Artifact
48+
49+from people.api import PersonResource
50+from project.api import SourcePackageResource
51+
52+
53+class TicketResource(ModelResource):
54+ owner = fields.ToOneField(PersonResource, 'owner', full=True)
55+
56+ class Meta:
57+ queryset = Ticket.objects.all()
58+ allowed_methods = ['get']
59+
60+
61+class SourcePackageUploadResource(ModelResource):
62+ sourcepackage = fields.ToOneField(SourcePackageResource, 'sourcepackage',
63+ full=True)
64+
65+ class Meta:
66+ queryset = SourcePackageUpload.objects.all()
67+ allowed_methods = ['get']
68+
69+
70+class SubTicketResource(ModelResource):
71+ assignee = fields.ToOneField(PersonResource, 'assignee', full=True)
72+ ticket = fields.ToOneField(TicketResource, 'ticket', full=True)
73+ source_package_upload = fields.ToOneField(SourcePackageUploadResource,
74+ 'source_package_upload',
75+ full=True)
76+
77+ class Meta:
78+ queryset = SubTicket.objects.all()
79+ allowed_methods = ['get']
80+
81+
82+class ArtifactResource(ModelResource):
83+ ticket_component = fields.ToOneField(SubTicketResource, 'ticket_component',
84+ full=True)
85+
86+ class Meta:
87+ queryset = Artifact.objects.all()
88+ allowed_methods = ['get']
89+
90+
91+class FullArtifactResource(ModelResource):
92+
93+ class Meta:
94+ queryset = Artifact.objects.all()
95+ allowed_methods = ['get']
96+
97+
98+class FullSubTicketResource(ModelResource):
99+ assignee = fields.ToOneField(PersonResource, 'assignee', full=True)
100+ source_package_upload = fields.ToOneField(SourcePackageUploadResource,
101+ 'source_package_upload',
102+ full=True)
103+ artifact = fields.ToManyField(FullArtifactResource, 'artifact_set',
104+ full=True)
105+
106+ class Meta:
107+ queryset = SubTicket.objects.all()
108+ allowed_methods = ['get']
109+
110+
111+class FullTicketResource(ModelResource):
112+ owner = fields.ToOneField(PersonResource, 'owner', full=True)
113+ subticket = fields.ToManyField(FullSubTicketResource, 'subticket_set',
114+ full=True)
115+
116+ class Meta:
117+ queryset = Ticket.objects.all()
118+ allowed_methods = ['get']
119
120=== modified file 'ticket_system/ticket/migrations/0001_initial.py'
121--- ticket_system/ticket/migrations/0001_initial.py 2013-12-11 17:18:58 +0000
122+++ ticket_system/ticket/migrations/0001_initial.py 2013-12-16 14:22:25 +0000
123@@ -44,7 +44,7 @@
124 db.create_table('artifact', (
125 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
126 ('type', self.gf('django.db.models.fields.CharField')(max_length=4096)),
127- ('ticket_component', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.SubTicket'], null=True, blank=True)),
128+ ('ticket_component', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.SubTicket'])),
129 ('reference', self.gf('django.db.models.fields.CharField')(max_length=4096)),
130 ('name', self.gf('django.db.models.fields.CharField')(max_length=4096)),
131 ))
132@@ -83,7 +83,7 @@
133 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
134 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
135 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
136- 'ticket_component': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']", 'null': 'True', 'blank': 'True'}),
137+ 'ticket_component': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']"}),
138 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
139 },
140 u'ticket.sourcepackageupload': {
141
142=== modified file 'ticket_system/ticket/models.py'
143--- ticket_system/ticket/models.py 2013-12-11 18:03:39 +0000
144+++ ticket_system/ticket/models.py 2013-12-16 14:22:25 +0000
145@@ -140,7 +140,7 @@
146 source_package_upload = models.ForeignKey(SourcePackageUpload)
147
148 def __unicode__(self):
149- return "{} {}".format(self.ticket, self.sourcepackageupload)
150+ return "{} {}".format(self.ticket, self.source_package_upload)
151
152
153 class Artifact(models.Model):
154@@ -153,7 +153,7 @@
155 ('LOGS', 'Logs'),
156 )
157 type = models.CharField(choices=ARTIFACT_TYPE, max_length=4096)
158- ticket_component = models.ForeignKey(SubTicket, blank=True, null=True)
159+ ticket_component = models.ForeignKey(SubTicket)
160 # 'reference' provided by the artifact manager
161 reference = models.CharField(max_length=4096)
162 name = models.CharField(max_length=4096)
163
164=== added directory 'ticket_system/ticket/tests'
165=== added file 'ticket_system/ticket/tests/__init__.py'
166--- ticket_system/ticket/tests/__init__.py 1970-01-01 00:00:00 +0000
167+++ ticket_system/ticket/tests/__init__.py 2013-12-16 14:22:25 +0000
168@@ -0,0 +1,18 @@
169+# Ubuntu Continuous Integration Engine
170+# Copyright 2013 Canonical Ltd.
171+
172+# This program is free software: you can redistribute it and/or modify it
173+# under the terms of the GNU Affero General Public License version 3, as
174+# published by the Free Software Foundation.
175+
176+# This program is distributed in the hope that it will be useful, but
177+# WITHOUT ANY WARRANTY; without even the implied warranties of
178+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
179+# PURPOSE. See the GNU Affero General Public License for more details.
180+
181+# You should have received a copy of the GNU Affero General Public License
182+# along with this program. If not, see <http://www.gnu.org/licenses/>.
183+
184+from test_models import *
185+from test_read_api import *
186+from test_full_read_api import *
187
188=== added file 'ticket_system/ticket/tests/test_full_read_api.py'
189--- ticket_system/ticket/tests/test_full_read_api.py 1970-01-01 00:00:00 +0000
190+++ ticket_system/ticket/tests/test_full_read_api.py 2013-12-16 14:22:25 +0000
191@@ -0,0 +1,126 @@
192+# Ubuntu Continuous Integration Engine
193+# Copyright 2013 Canonical Ltd.
194+
195+# This program is free software: you can redistribute it and/or modify it
196+# under the terms of the GNU Affero General Public License version 3, as
197+# published by the Free Software Foundation.
198+
199+# This program is distributed in the hope that it will be useful, but
200+# WITHOUT ANY WARRANTY; without even the implied warranties of
201+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
202+# PURPOSE. See the GNU Affero General Public License for more details.
203+
204+# You should have received a copy of the GNU Affero General Public License
205+# along with this program. If not, see <http://www.gnu.org/licenses/>.
206+
207+from model_mommy import mommy
208+from ci_utils.tastypie.test import TastypieTestCase
209+
210+
211+class TicketReadAPITest(TastypieTestCase):
212+
213+ def setUp(self):
214+ super(TicketReadAPITest, self).setUp('/api/v1')
215+ self.person = mommy.make('Person')
216+ self.ticket = mommy.make('Ticket', owner=self.person)
217+ self.sourcepackage = mommy.make('SourcePackage')
218+ self.spu = mommy.make('SourcePackageUpload',
219+ sourcepackage=self.sourcepackage)
220+ self.subticket = mommy.make('SubTicket', ticket=self.ticket,
221+ assignee=self.person,
222+ source_package_upload=self.spu)
223+ self.artifact = mommy.make('Artifact', ticket_component=self.subticket)
224+ self.maxDiff = None
225+
226+ def test_get_fullticket_api(self):
227+ obj = self.getResource('fullticket/')
228+ self.assertEqual(obj['objects'][0], {
229+ u'status': self.ticket.status,
230+ u'current_workflow_step': self.ticket.current_workflow_step,
231+ u'description': unicode(self.ticket.description),
232+ u'title': unicode(self.ticket.title),
233+ u'subticket': [{
234+ u'status': self.subticket.status,
235+ u'current_workflow_step': self.subticket.current_workflow_step,
236+ u'artifact': [{
237+ u'resource_uri': u'/api/v1/fullartifact/{0}/'.format(
238+ self.artifact.pk),
239+ u'type': unicode(self.artifact.type),
240+ u'id': self.artifact.pk,
241+ u'reference': unicode(self.artifact.reference),
242+ u'name': unicode(self.artifact.name)}],
243+ u'assignee': {
244+ u'resource_uri': u'/api/v1/person/{0}/'.format(
245+ self.person.pk),
246+ u'is_team': self.person.is_team,
247+ u'email': unicode(self.person.email),
248+ u'name': unicode(self.person.name),
249+ u'id': self.person.pk},
250+ u'source_package_upload': {
251+ u'version': unicode(self.spu.version),
252+ u'sourcepackage': {
253+ u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
254+ self.sourcepackage.pk),
255+ u'id': self.sourcepackage.pk,
256+ u'name': unicode(self.sourcepackage.name)},
257+ u'id': self.spu.pk,
258+ u'resource_uri':
259+ u'/api/v1/sourcepackageupload/{0}/'.format(self.spu.pk)},
260+ u'id': self.subticket.pk,
261+ u'resource_uri': u'/api/v1/fullsubticket/{0}/'.format(
262+ self.subticket.pk)}],
263+ u'bug_id': self.ticket.bug_id,
264+ u'owner': {
265+ u'resource_uri': u'/api/v1/person/{0}/'.format(self.person.pk),
266+ u'is_team': self.person.is_team,
267+ u'email': unicode(self.person.email),
268+ u'name': unicode(self.person.name),
269+ u'id': self.person.pk},
270+ u'base_image': unicode(self.ticket.base_image),
271+ u'id': self.ticket.pk,
272+ u'resource_uri': u'/api/v1/fullticket/{0}/'.format(self.ticket.pk),
273+ })
274+
275+ def test_get_fullartifact_api(self):
276+ obj = self.getResource('fullartifact/')
277+ self.assertEqual(obj['objects'][0], {
278+ u'resource_uri': u'/api/v1/fullartifact/{0}/'.format(
279+ self.artifact.pk),
280+ u'type': unicode(self.artifact.type),
281+ u'id': self.artifact.pk,
282+ u'reference': unicode(self.artifact.reference),
283+ u'name': unicode(self.artifact.name),
284+ })
285+
286+ def test_get_fullsubticket_api(self):
287+ obj = self.getResource('fullsubticket/')
288+ self.assertEqual(obj['objects'][0], {
289+ u'status': self.subticket.status,
290+ u'current_workflow_step': self.subticket.current_workflow_step,
291+ u'artifact': [{
292+ u'resource_uri': u'/api/v1/fullartifact/{0}/'.format(
293+ self.artifact.pk),
294+ u'type': unicode(self.artifact.type),
295+ u'id': self.artifact.pk,
296+ u'reference': unicode(self.artifact.reference),
297+ u'name': unicode(self.artifact.name)}],
298+ u'assignee': {
299+ u'resource_uri': u'/api/v1/person/{0}/'.format(self.person.pk),
300+ u'is_team': self.person.is_team,
301+ u'email': unicode(self.person.email),
302+ u'name': unicode(self.person.name),
303+ u'id': self.person.pk},
304+ u'source_package_upload': {
305+ u'version': unicode(self.spu.version),
306+ u'sourcepackage': {
307+ u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
308+ self.sourcepackage.pk),
309+ u'id': self.sourcepackage.pk,
310+ u'name': unicode(self.sourcepackage.name)},
311+ u'id': self.spu.pk,
312+ u'resource_uri':
313+ u'/api/v1/sourcepackageupload/{0}/'.format(self.spu.pk)},
314+ u'id': self.subticket.pk,
315+ u'resource_uri': u'/api/v1/fullsubticket/{0}/'.format(
316+ self.subticket.pk),
317+ })
318
319=== renamed file 'ticket_system/ticket/tests.py' => 'ticket_system/ticket/tests/test_models.py'
320=== added file 'ticket_system/ticket/tests/test_read_api.py'
321--- ticket_system/ticket/tests/test_read_api.py 1970-01-01 00:00:00 +0000
322+++ ticket_system/ticket/tests/test_read_api.py 2013-12-16 14:22:25 +0000
323@@ -0,0 +1,163 @@
324+# Ubuntu Continuous Integration Engine
325+# Copyright 2013 Canonical Ltd.
326+
327+# This program is free software: you can redistribute it and/or modify it
328+# under the terms of the GNU Affero General Public License version 3, as
329+# published by the Free Software Foundation.
330+
331+# This program is distributed in the hope that it will be useful, but
332+# WITHOUT ANY WARRANTY; without even the implied warranties of
333+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
334+# PURPOSE. See the GNU Affero General Public License for more details.
335+
336+# You should have received a copy of the GNU Affero General Public License
337+# along with this program. If not, see <http://www.gnu.org/licenses/>.
338+
339+from model_mommy import mommy
340+from ci_utils.tastypie.test import TastypieTestCase
341+
342+
343+class TicketReadAPITest(TastypieTestCase):
344+
345+ def setUp(self):
346+ super(TicketReadAPITest, self).setUp('/api/v1')
347+ self.person = mommy.make('Person')
348+ self.ticket = mommy.make('Ticket', owner=self.person)
349+ self.sourcepackage = mommy.make('SourcePackage')
350+ self.spu = mommy.make('SourcePackageUpload',
351+ sourcepackage=self.sourcepackage)
352+ self.subticket = mommy.make('SubTicket', ticket=self.ticket,
353+ assignee=self.person,
354+ source_package_upload=self.spu)
355+ self.artifact = mommy.make('Artifact', ticket_component=self.subticket)
356+
357+ def test_get_artifact_api(self):
358+ obj = self.getResource('artifact/')
359+ self.assertEqual(obj['objects'][0], {
360+ u'name': unicode(self.artifact.name),
361+ u'reference': unicode(self.artifact.reference),
362+ u'ticket_component': {
363+ u'status': self.subticket.status,
364+ u'current_workflow_step': self.subticket.current_workflow_step,
365+ u'assignee': {
366+ u'resource_uri': u'/api/v1/person/{0}/'.format(
367+ self.person.pk),
368+ u'is_team': self.person.is_team,
369+ u'email': unicode(self.person.email),
370+ u'name': unicode(self.person.name),
371+ u'id': self.person.pk},
372+ u'source_package_upload': {
373+ u'version': unicode(self.spu.version),
374+ u'sourcepackage': {
375+ u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
376+ self.sourcepackage.pk),
377+ u'id': self.sourcepackage.pk,
378+ u'name': unicode(self.sourcepackage.name)},
379+ u'id': self.spu.pk,
380+ u'resource_uri':
381+ u'/api/v1/sourcepackageupload/{0}/'.format(self.spu.pk)},
382+ u'ticket': {
383+ u'status': self.ticket.status,
384+ u'current_workflow_step':
385+ self.ticket.current_workflow_step,
386+ u'description': unicode(self.ticket.description),
387+ u'title': unicode(self.ticket.title),
388+ u'bug_id': self.ticket.bug_id,
389+ u'owner': {
390+ u'resource_uri': u'/api/v1/person/{0}/'.format(
391+ self.person.pk),
392+ u'is_team': self.person.is_team,
393+ u'email': unicode(self.person.email),
394+ u'name': unicode(self.person.name),
395+ u'id': self.person.pk},
396+ u'base_image': unicode(self.ticket.base_image),
397+ u'id': self.ticket.pk,
398+ u'resource_uri': u'/api/v1/ticket/{0}/'.format(
399+ self.ticket.pk)},
400+ u'id': self.subticket.pk,
401+ u'resource_uri': u'/api/v1/subticket/{0}/'.format(
402+ self.subticket.pk)},
403+ u'type': unicode(self.artifact.type),
404+ u'id': self.artifact.pk,
405+ u'resource_uri': u'/api/v1/artifact/{0}/'.format(self.artifact.pk),
406+ })
407+
408+ def test_get_subticket_api(self):
409+ obj = self.getResource('subticket/')
410+ self.assertEqual(obj['objects'][0], {
411+ u'status': self.subticket.status,
412+ u'current_workflow_step': self.subticket.current_workflow_step,
413+ u'assignee': {
414+ u'resource_uri': u'/api/v1/person/{0}/'.format(
415+ self.person.pk),
416+ u'is_team': self.person.is_team,
417+ u'email': unicode(self.person.email),
418+ u'name': unicode(self.person.name),
419+ u'id': self.person.pk},
420+ u'source_package_upload': {
421+ u'version': unicode(self.spu.version),
422+ u'sourcepackage': {
423+ u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
424+ self.sourcepackage.pk),
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/sourcepackageupload/{0}/'.format(self.spu.pk)},
430+ u'ticket': {
431+ u'status': self.ticket.status,
432+ u'current_workflow_step':
433+ self.ticket.current_workflow_step,
434+ u'description': unicode(self.ticket.description),
435+ u'title': unicode(self.ticket.title),
436+ u'bug_id': self.ticket.bug_id,
437+ u'owner': {
438+ u'resource_uri': u'/api/v1/person/{0}/'.format(
439+ self.person.pk),
440+ u'is_team': self.person.is_team,
441+ u'email': unicode(self.person.email),
442+ u'name': unicode(self.person.name),
443+ u'id': self.person.pk},
444+ u'base_image': unicode(self.ticket.base_image),
445+ u'id': self.ticket.pk,
446+ u'resource_uri': u'/api/v1/ticket/{0}/'.format(
447+ self.ticket.pk)},
448+ u'id': self.subticket.pk,
449+ u'resource_uri': u'/api/v1/subticket/{0}/'.format(
450+ self.subticket.pk),
451+ })
452+
453+ def test_get_ticket_api(self):
454+ obj = self.getResource('ticket/')
455+ self.assertEqual(obj['objects'][0], {
456+ u'status': self.ticket.status,
457+ u'current_workflow_step':
458+ self.ticket.current_workflow_step,
459+ u'description': unicode(self.ticket.description),
460+ u'title': unicode(self.ticket.title),
461+ u'bug_id': self.ticket.bug_id,
462+ u'owner': {
463+ u'resource_uri': u'/api/v1/person/{0}/'.format(self.person.pk),
464+ u'is_team': self.person.is_team,
465+ u'email': unicode(self.person.email),
466+ u'name': unicode(self.person.name),
467+ u'id': self.person.pk},
468+ u'base_image': unicode(self.ticket.base_image),
469+ u'id': self.ticket.pk,
470+ u'resource_uri': u'/api/v1/ticket/{0}/'.format(
471+ self.ticket.pk),
472+ })
473+
474+ def test_get_sourcepackageupload_api(self):
475+ obj = self.getResource('sourcepackageupload/')
476+ self.assertEqual(obj['objects'][0], {
477+ u'version': unicode(self.spu.version),
478+ u'sourcepackage': {
479+ u'resource_uri': u'/api/v1/sourcepackage/{0}/'.format(
480+ self.sourcepackage.pk),
481+ u'id': self.sourcepackage.pk,
482+ u'name': unicode(self.sourcepackage.name)},
483+ u'id': self.spu.pk,
484+ u'resource_uri':
485+ u'/api/v1/sourcepackageupload/{0}/'.format(self.spu.pk),
486+ })
487
488=== modified file 'ticket_system/ticket_system/urls.py'
489--- ticket_system/ticket_system/urls.py 2013-12-09 19:35:22 +0000
490+++ ticket_system/ticket_system/urls.py 2013-12-16 14:22:25 +0000
491@@ -18,12 +18,22 @@
492 from tastypie.api import Api
493 from people.api import PersonResource
494 from project.api import SourcePackageResource, BinaryPackageResource
495+from ticket.api import (TicketResource, SubTicketResource, ArtifactResource,
496+ SourcePackageUploadResource, FullSubTicketResource,
497+ FullTicketResource, FullArtifactResource)
498
499 admin.autodiscover()
500 v1_api = Api(api_name='v1')
501 v1_api.register(PersonResource())
502 v1_api.register(SourcePackageResource())
503 v1_api.register(BinaryPackageResource())
504+v1_api.register(TicketResource())
505+v1_api.register(SubTicketResource())
506+v1_api.register(SourcePackageUploadResource())
507+v1_api.register(ArtifactResource())
508+v1_api.register(FullArtifactResource())
509+v1_api.register(FullSubTicketResource())
510+v1_api.register(FullTicketResource())
511
512 urlpatterns = patterns(
513 '',

Subscribers

People subscribed via source and target branches