Merge lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-read-api into lp:ubuntu-ci-services-itself
- ts-ticket-read-api
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ursula Junque (community) | Approve | ||
Francis Ginther | Approve | ||
Review via email:
|
Commit message
Add read API to tickets
Description of the change
- 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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ursula Junque (ursinha) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) wrote : | # |
Francis, how will the ticket statuses be kept up to date?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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"}
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) wrote : | # |
Ok, so the TS will be polling the message queue looking for status changes?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 :))
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:
Its juju deployable with:
I'm guessing your "on_message" calls would basically be updates to the
django model or something
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
Preview Diff
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 | '', |
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,