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

Proposed by Chris Johnston
Status: Merged
Approved by: Chris Johnston
Approved revision: 26
Merged at revision: 25
Proposed branch: lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-models
Merge into: lp:ubuntu-ci-services-itself
Diff against target: 563 lines (+470/-6)
12 files modified
ticket_system/people/__init__.py (+0/-1)
ticket_system/people/admin.py (+0/-1)
ticket_system/people/api.py (+0/-1)
ticket_system/people/models.py (+0/-1)
ticket_system/people/tests.py (+0/-1)
ticket_system/ticket/__init__.py (+14/-0)
ticket_system/ticket/admin.py (+45/-0)
ticket_system/ticket/migrations/0001_initial.py (+117/-0)
ticket_system/ticket/models.py (+162/-0)
ticket_system/ticket/tests.py (+131/-0)
ticket_system/ticket_system/local_tests.py (+0/-1)
ticket_system/ticket_system/settings.py (+1/-0)
To merge this branch: bzr merge lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-models
Reviewer Review Type Date Requested Status
Andy Doan (community) Approve
Chris Johnston (community) Needs Resubmitting
Review via email: mp+198454@code.launchpad.net

Commit message

Add ticket related models to the ticket system

To post a comment you must log in.
Revision history for this message
Andy Doan (doanac) wrote :
Download full text (4.1 KiB)

On 12/10/2013 01:23 PM, Chris Johnston wrote:
> Chris Johnston has proposed merging lp:~cjohnston/ubuntu-ci-services-itself/ts-ticket-models into lp:ubuntu-ci-services-itself.

> === added file 'ticket_system/ticket/models.py'

> +class Ticket(models.Model):
> + class Meta:
> + db_table = 'ticket'
> +
> + WORKFLOW_STEPS = (
> + ("PACKAGE_BUILDING", "Package building"),
> + ("IMAGE_BUILDING", "Image building"),
> + ("IMAGE_TESTING", "Image testing"),
> + ("PACKAGE_PUBLISHING", "Package publishing"),
> + )
> + WORKFLOW_STEP_STATUSES = (
> + ("PKG_BUILD_WAITING", "Not started"),
> + ("PKG_BUILD_INPROGRESS", "In progress"),
> + ("PKG_BUILD_DONE", "Completed"),
> + ("PKG_BUILD_FAILED", "Failed"),
> + ("IMAGE_BUILD_WAITING", "Not started"),
> + ("IMAGE_BUILD_INPROGRESS", "In progress"),
> + ("IMAGE_BUILD_DONE", "Completed"),
> + ("IMAGE_BUILD_FAILED", "Failed"),
> + ("IMAGE_TESTS_WAITING", "Not started"),
> + ("IMAGE_TESTS_INPROGRESS", "In progress"),
> + ("IMAGE_TESTS_PASSED", "Passed"),
> + ("IMAGE_TESTS_FAILED", "Failed"),
> + ("PKG_PUBLISHING_DONE", "Completed"),
> + ("PKG_PUBLISHING_FAILED", "Failed"),
> + )

> + current_workflow_step = models.CharField(choices=WORKFLOW_STEPS,
> + max_length=4096)
> + status = models.CharField(choices=WORKFLOW_STEP_STATUSES,
> + max_length=4096)

Would it make sense to use models.IntegerField and change these values
from strings to integers. I think that would then allow you do
eventually have logic like:

  if ticket.status < Ticket.IMAGE_BUILD_DONE:
      print("No image available for ticket")

I'd define these status as multiples of 10 like:
   PKG_BUILD_WAITING = 0
   PKG_BUILD_INPROGRESS = 10

because I could forsee a day when we might need new steps in the process.

> +class SourcePackageUpload(models.Model):
> + class Meta:
> + db_table = 'sourcepackageupload'
> +
> + sourcepackage = models.ForeignKey(SourcePackage)
> + version = models.CharField(max_length=4096)
> +
> + def __unicode__(self):
> + return "{} {}".format(self.sourcepackage, self.version)
> +
> +
> +class SubTicket(models.Model):
> + class Meta:
> + db_table = 'subticket'
> +
> + WORKFLOW_STEPS = (
> + ("WAITING", "Not started"),
> + ("PACKAGE_BUILDING", "Package building"),
> + ("COMPLETE", "Completed"),
> + )
> + WORKFLOW_STEP_STATUSES = (
> + ("PKG_BUILD_WAITING", "Not started"),
> + ("PKG_BUILD_INPROGRESS", "In progress"),
> + ("PKG_BUILD_DONE", "Completed"),
> + ("PKG_BUILD_FAILED", "Failed"),
> + )

same point about integer field here.

> + assignee = models.ForeignKey(Person)

I assume we want subtickets to be able to have different owners than the
main ticket?

> + sourcepackageupload = models.ForeignKey(SourcePackageUpload)

> +class Artifact(models.Model):

> + ticket_component = models.ForeignKey(SubTicket, blank=True, null=True)
> + sourcepackageupload = models.ForeignKey(
> + SourcePacka...

Read more...

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

On Tue, Dec 10, 2013 at 4:09 PM, Andy Doan <email address hidden>wrote:

> > + assignee = models.ForeignKey(Person)
>
> I assume we want subtickets to be able to have different owners than the
> main ticket?
>
>
Yes. If there are multiple people working on different parts of a ticket.
This is probably more for the future, but no sense in not adding it now.

> + sourcepackageupload = models.ForeignKey(SourcePackageUpload)
>
> > +class Artifact(models.Model):
>
> > + ticket_component = models.ForeignKey(SubTicket, blank=True,
> null=True)
> > + sourcepackageupload = models.ForeignKey(
> > + SourcePackageUpload, null=True, blank=True,
> > + verbose_name='Source Package Upload')
>
> This feels like we might not have a relation defined properly. It seems
> like an Artifact shouldn't need the "ticket_component" attribute. I
> think a SubTicket has a SourcePackageUpload which has artifacts. So
> adding this ticket_component here feels like something isn't properly
> normalized. I'm not sure the intent but it seems like it should be one
> of these options:
>
> = 1 ======
> You add to SourcePackageUpload:
> subticket = models.ForeignKey(SubTicket)
>
> Remove the the "ticket_component" attribute from Artifact.
>
> The drawback here, is that a SubTicket could have more than
> SourcePackageUploads. Maybe a OneToOneField is needed instead?
>
> = 2 =====
>
> You don't need a SourcePackage upload class. You just make
> "source_package" and "source_package_version" as attributes to
> SubTicket. Then Arfifact just has a foreign key to SubTicket.
>
> = 3 =====
>
> This is already correct, and I'm missing something.
>

An artifact can be two different things. When you upload a source package
at the initial time of creating a ticket, it is an artifact (it needs to be
stored somewhere, and we need to be able to tell other parts of the CI
engine where to look for the source package). As the system goes through
and does things, there (I assume) will be output files (logs and such,
maybe videos when using autopilot, etc). These things don't belong to a
package upload, they belong to a ticket. So, your artifact can either be
the source package or an output artifact from the CI engine.

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

On 12/10/2013 03:18 PM, Chris Johnston wrote:
> An artifact can be two different things. When you upload a source package
> at the initial time of creating a ticket, it is an artifact (it needs to be
> stored somewhere, and we need to be able to tell other parts of the CI
> engine where to look for the source package). As the system goes through
> and does things, there (I assume) will be output files (logs and such,
> maybe videos when using autopilot, etc). These things don't belong to a
> package upload, they belong to a ticket. So, your artifact can either be
> the source package or an output artifact from the CI engine.

So I think this means you don't want/need the
Artifact.soucepackageupload attribute.

If you wanted to know the source package uploads for a subticket you
could still get them with:

  subticket = <something>
  uploads = Artifact.objects.filter(
      ticket_component=subticket, type=Artfifact.SPU)

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

Made changes to the choices fields and made them Integers

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

The "COMPLETE = 001" feels odd. We've got a set of increasing values except for this one value? Seems like COMPLETE in this case would be the biggest value? Also, be careful with padding things with 0's. Thats actual octal 1 which just happens to be 1. But if you did "010" you'd actually have 8 in base 10. Maybe reversing the order of the constants would make the COMPLETE thing nice where COMPLETE = 0 and WAITING = 500.

264 +WAITING = 000
265 +PACKAGE_BUILDING = 100
268 +PACKAGE_PUBLISHING = 400
269 +COMPLETE = 001
270 +PKG_BUILD_WAITING = 110

Last note is just styling - you can take it or leave it. Having all these constants defined outside the Ticket class means modules will have to do an akward import like:

 import ticket_system.models as models
 models.Ticket.objects.filter(status=models.WAITING)

Where if they instead were in the Ticket class you could do:

 from ticket_system.models import Ticket
 Ticket.objects.filter(status=Ticket.WAITING)

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

Now to understand SubTicket.sourcepackageupload.

1) why do we not use underscores here?

2) To confirm our relationships:

 a) a SubTicket _has_ a SourcePackageUpload
 b) a SourcePackageUpload can have many Artifacts

is this correct?

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

> 2) To confirm our relationships:
>
> a) a SubTicket _has_ a SourcePackageUpload
> b) a SourcePackageUpload can have many Artifacts
>
> is this correct?

That is correct. A ticket corresponds to a build request, which could contain one or more source packages. A subticket would be associated with each source package. A source package contains multiple files to upload (a .dsc, a orig.tar.gz, etc).

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

On 12/10/2013 10:25 PM, Francis Ginther wrote:
>> 2) To confirm our relationships:
>>
>> a) a SubTicket _has_ a SourcePackageUpload
>> b) a SourcePackageUpload can have many Artifacts
>>
>> is this correct?
>
> That is correct. A ticket corresponds to a build request, which could contain one or more source packages. A subticket would be associated with each source package. A source package contains multiple files to upload (a .dsc, a orig.tar.gz, etc).

Awesome. I'm +1 for the model in general now. Still have my concerns
about the status codes.

24. By Chris Johnston

change constants per review

25. By Chris Johnston

Fix tests

Revision history for this message
Chris Johnston (cjohnston) :
review: Needs Resubmitting
Revision history for this message
Ursula Junque (ursinha) wrote :

Hey Chris, only a few notes:

322 + (COMPLETED, "COMPLETED"),

I think this label should be "Completed".

456 +def create_ticket(title="This is my ticket",
457 + description="this is my ticket description",
458 + bug_id='12345', owner='Chris Johnston',
459 + current_workflow_step=200,
460 + status=220, base_image='17'):

Can we use the constants here (and other places that refer to it) instead of the integer, like Ticket.PKG_BUILDING, Ticket.PKG_BUILDING_INPROGRESS?

I think it looks good for now. If we detect these workflow steps/statuses aren't precise enough we can easily add more of them.

26. By Chris Johnston

Test changes per review

Revision history for this message
Andy Doan (doanac) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ticket_system/people/__init__.py'
2--- ticket_system/people/__init__.py 2013-12-09 17:43:12 +0000
3+++ ticket_system/people/__init__.py 2013-12-11 18:03:47 +0000
4@@ -1,4 +1,3 @@
5-# Houston
6 # Ubuntu Continuous Integration Engine
7 # Copyright 2013 Canonical Ltd.
8
9
10=== modified file 'ticket_system/people/admin.py'
11--- ticket_system/people/admin.py 2013-12-09 17:43:12 +0000
12+++ ticket_system/people/admin.py 2013-12-11 18:03:47 +0000
13@@ -1,4 +1,3 @@
14-# Houston
15 # Ubuntu Continuous Integration Engine
16 # Copyright 2013 Canonical Ltd.
17
18
19=== modified file 'ticket_system/people/api.py'
20--- ticket_system/people/api.py 2013-12-09 17:43:12 +0000
21+++ ticket_system/people/api.py 2013-12-11 18:03:47 +0000
22@@ -1,4 +1,3 @@
23-# Houston
24 # Ubuntu Continuous Integration Engine
25 # Copyright 2013 Canonical Ltd.
26
27
28=== modified file 'ticket_system/people/models.py'
29--- ticket_system/people/models.py 2013-12-09 18:57:54 +0000
30+++ ticket_system/people/models.py 2013-12-11 18:03:47 +0000
31@@ -1,4 +1,3 @@
32-# Houston
33 # Ubuntu Continuous Integration Engine
34 # Copyright 2013 Canonical Ltd.
35
36
37=== modified file 'ticket_system/people/tests.py'
38--- ticket_system/people/tests.py 2013-12-10 18:34:08 +0000
39+++ ticket_system/people/tests.py 2013-12-11 18:03:47 +0000
40@@ -1,4 +1,3 @@
41-# Houston
42 # Ubuntu Continuous Integration Engine
43 # Copyright 2013 Canonical Ltd.
44
45
46=== added directory 'ticket_system/ticket'
47=== added file 'ticket_system/ticket/__init__.py'
48--- ticket_system/ticket/__init__.py 1970-01-01 00:00:00 +0000
49+++ ticket_system/ticket/__init__.py 2013-12-11 18:03:47 +0000
50@@ -0,0 +1,14 @@
51+# Ubuntu Continuous Integration Engine
52+# Copyright 2013 Canonical Ltd.
53+
54+# This program is free software: you can redistribute it and/or modify it
55+# under the terms of the GNU Affero General Public License version 3, as
56+# published by the Free Software Foundation.
57+
58+# This program is distributed in the hope that it will be useful, but
59+# WITHOUT ANY WARRANTY; without even the implied warranties of
60+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
61+# PURPOSE. See the GNU Affero General Public License for more details.
62+
63+# You should have received a copy of the GNU Affero General Public License
64+# along with this program. If not, see <http://www.gnu.org/licenses/>.
65
66=== added file 'ticket_system/ticket/admin.py'
67--- ticket_system/ticket/admin.py 1970-01-01 00:00:00 +0000
68+++ ticket_system/ticket/admin.py 2013-12-11 18:03:47 +0000
69@@ -0,0 +1,45 @@
70+# Ubuntu Continuous Integration Engine
71+# Copyright 2013 Canonical Ltd.
72+
73+# This program is free software: you can redistribute it and/or modify it
74+# under the terms of the GNU Affero General Public License version 3, as
75+# published by the Free Software Foundation.
76+
77+# This program is distributed in the hope that it will be useful, but
78+# WITHOUT ANY WARRANTY; without even the implied warranties of
79+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
80+# PURPOSE. See the GNU Affero General Public License for more details.
81+
82+# You should have received a copy of the GNU Affero General Public License
83+# along with this program. If not, see <http://www.gnu.org/licenses/>.
84+
85+from django.contrib import admin
86+from ticket.models import Ticket, SubTicket, SourcePackageUpload, Artifact
87+
88+
89+class TicketAdmin(admin.ModelAdmin):
90+ list_filter = ['current_workflow_step', 'status']
91+ search_fields = ['title', 'bug_id']
92+ list_display = ('title', 'current_workflow_step', 'status', 'base_image')
93+
94+
95+class SubTicketAdmin(admin.ModelAdmin):
96+ list_filter = ['status']
97+ search_fields = ['ticket', 'sourcepackageupload']
98+ list_display = ('ticket', 'status', 'assignee', 'sourcepackageupload')
99+
100+
101+class SourcePackageUploadAdmin(admin.ModelAdmin):
102+ search_fields = ['sourcepackage']
103+ list_display = ('sourcepackage', 'version')
104+
105+
106+class ArtifactAdmin(admin.ModelAdmin):
107+ list_filter = ['type']
108+ search_fields = ['name']
109+ list_display = ('name', 'ticket_component', 'type')
110+
111+admin.site.register(Ticket, TicketAdmin)
112+admin.site.register(SubTicket, SubTicketAdmin)
113+admin.site.register(SourcePackageUpload, SourcePackageUploadAdmin)
114+admin.site.register(Artifact, ArtifactAdmin)
115
116=== added directory 'ticket_system/ticket/migrations'
117=== added file 'ticket_system/ticket/migrations/0001_initial.py'
118--- ticket_system/ticket/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
119+++ ticket_system/ticket/migrations/0001_initial.py 2013-12-11 18:03:47 +0000
120@@ -0,0 +1,117 @@
121+# -*- coding: utf-8 -*-
122+import datetime
123+from south.db import db
124+from south.v2 import SchemaMigration
125+from django.db import models
126+
127+
128+class Migration(SchemaMigration):
129+
130+ def forwards(self, orm):
131+ # Adding model 'Ticket'
132+ db.create_table('ticket', (
133+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
134+ ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['people.Person'])),
135+ ('title', self.gf('django.db.models.fields.CharField')(max_length=4096)),
136+ ('description', self.gf('django.db.models.fields.TextField')()),
137+ ('bug_id', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
138+ ('current_workflow_step', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
139+ ('status', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
140+ ('base_image', self.gf('django.db.models.fields.CharField')(max_length=4096)),
141+ ))
142+ db.send_create_signal(u'ticket', ['Ticket'])
143+
144+ # Adding model 'SourcePackageUpload'
145+ db.create_table('sourcepackageupload', (
146+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
147+ ('sourcepackage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['project.SourcePackage'])),
148+ ('version', self.gf('django.db.models.fields.CharField')(max_length=4096)),
149+ ))
150+ db.send_create_signal(u'ticket', ['SourcePackageUpload'])
151+
152+ # Adding model 'SubTicket'
153+ db.create_table('subticket', (
154+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
155+ ('current_workflow_step', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
156+ ('status', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
157+ ('ticket', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.Ticket'])),
158+ ('assignee', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['people.Person'])),
159+ ('source_package_upload', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.SourcePackageUpload'])),
160+ ))
161+ db.send_create_signal(u'ticket', ['SubTicket'])
162+
163+ # Adding model 'Artifact'
164+ db.create_table('artifact', (
165+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
166+ ('type', self.gf('django.db.models.fields.CharField')(max_length=4096)),
167+ ('ticket_component', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.SubTicket'], null=True, blank=True)),
168+ ('reference', self.gf('django.db.models.fields.CharField')(max_length=4096)),
169+ ('name', self.gf('django.db.models.fields.CharField')(max_length=4096)),
170+ ))
171+ db.send_create_signal(u'ticket', ['Artifact'])
172+
173+
174+ def backwards(self, orm):
175+ # Deleting model 'Ticket'
176+ db.delete_table('ticket')
177+
178+ # Deleting model 'SourcePackageUpload'
179+ db.delete_table('sourcepackageupload')
180+
181+ # Deleting model 'SubTicket'
182+ db.delete_table('subticket')
183+
184+ # Deleting model 'Artifact'
185+ db.delete_table('artifact')
186+
187+
188+ models = {
189+ u'people.person': {
190+ 'Meta': {'object_name': 'Person', 'db_table': "'person'"},
191+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '4096'}),
192+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
193+ 'is_team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
194+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
195+ },
196+ u'project.sourcepackage': {
197+ 'Meta': {'object_name': 'SourcePackage', 'db_table': "'sourcepackage'"},
198+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
199+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
200+ },
201+ u'ticket.artifact': {
202+ 'Meta': {'object_name': 'Artifact', 'db_table': "'artifact'"},
203+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
205+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
206+ 'ticket_component': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']", 'null': 'True', 'blank': 'True'}),
207+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
208+ },
209+ u'ticket.sourcepackageupload': {
210+ 'Meta': {'object_name': 'SourcePackageUpload', 'db_table': "'sourcepackageupload'"},
211+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
212+ 'sourcepackage': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['project.SourcePackage']"}),
213+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
214+ },
215+ u'ticket.subticket': {
216+ 'Meta': {'object_name': 'SubTicket', 'db_table': "'subticket'"},
217+ 'assignee': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['people.Person']"}),
218+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
219+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
220+ 'source_package_upload': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SourcePackageUpload']"}),
221+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
222+ 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"})
223+ },
224+ u'ticket.ticket': {
225+ 'Meta': {'object_name': 'Ticket', 'db_table': "'ticket'"},
226+ 'base_image': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
227+ 'bug_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
228+ 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
229+ 'description': ('django.db.models.fields.TextField', [], {}),
230+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
231+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['people.Person']"}),
232+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
233+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
234+ }
235+ }
236+
237+ complete_apps = ['ticket']
238\ No newline at end of file
239
240=== added file 'ticket_system/ticket/migrations/__init__.py'
241=== added file 'ticket_system/ticket/models.py'
242--- ticket_system/ticket/models.py 1970-01-01 00:00:00 +0000
243+++ ticket_system/ticket/models.py 2013-12-11 18:03:47 +0000
244@@ -0,0 +1,162 @@
245+# Ubuntu Continuous Integration Engine
246+# Copyright 2013 Canonical Ltd.
247+
248+# This program is free software: you can redistribute it and/or modify it
249+# under the terms of the GNU Affero General Public License version 3, as
250+# published by the Free Software Foundation.
251+
252+# This program is distributed in the hope that it will be useful, but
253+# WITHOUT ANY WARRANTY; without even the implied warranties of
254+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
255+# PURPOSE. See the GNU Affero General Public License for more details.
256+
257+# You should have received a copy of the GNU Affero General Public License
258+# along with this program. If not, see <http://www.gnu.org/licenses/>.
259+
260+from django.db import models
261+from people.models import Person
262+from project.models import SourcePackage
263+
264+
265+class Ticket(models.Model):
266+ class Meta:
267+ db_table = 'ticket'
268+
269+ NEW = 000
270+ QUEUED = 100
271+ PENDING = 110
272+ COMPLETED = 1000
273+
274+ PKG_BUILDING = 200
275+ PKG_BUILDING_WAITING = 210
276+ PKG_BUILDING_INPROGRESS = 220
277+ PKG_BUILDING_COMPLETED = 230
278+ PKG_BUILDING_FAILED = 240
279+
280+ IMAGE_BUILDING = 300
281+ IMAGE_BUILDING_WAITING = 310
282+ IMAGE_BUILDING_INPROGRESS = 320
283+ IMAGE_BUILDING_COMPLETED = 330
284+ IMAGE_BUILDING_FAILED = 340
285+
286+ IMAGE_TESTING = 400
287+ IMAGE_TESTING_WAITING = 410
288+ IMAGE_TESTING_INPROGRESS = 420
289+ IMAGE_TESTING_PASSED = 430
290+ IMAGE_TESTING_FAILED = 440
291+
292+ PKG_PUBLISHING = 500
293+ PKG_PUBLISHING_COMPLETED = 510
294+ PKG_PUBLISHING_FAILED = 520
295+
296+ WORKFLOW_STEPS = (
297+ (NEW, "New"),
298+ (QUEUED, "Queued"),
299+ (PKG_BUILDING, "Package building"),
300+ (IMAGE_BUILDING, "Image building"),
301+ (IMAGE_TESTING, "Image testing"),
302+ (PKG_PUBLISHING, "Package publishing"),
303+ (COMPLETED, "Completed"),
304+ )
305+ WORKFLOW_STEP_STATUSES = (
306+ (NEW, "New"),
307+ (PENDING, "Not started"),
308+ (PKG_BUILDING_WAITING, "Not started"),
309+ (PKG_BUILDING_INPROGRESS, "In progress"),
310+ (PKG_BUILDING_COMPLETED, "Completed"),
311+ (PKG_BUILDING_FAILED, "Failed"),
312+ (IMAGE_BUILDING_WAITING, "Not started"),
313+ (IMAGE_BUILDING_INPROGRESS, "In progress"),
314+ (IMAGE_BUILDING_COMPLETED, "Completed"),
315+ (IMAGE_BUILDING_FAILED, "Failed"),
316+ (IMAGE_TESTING_WAITING, "Not started"),
317+ (IMAGE_TESTING_INPROGRESS, "In progress"),
318+ (IMAGE_TESTING_PASSED, "Passed"),
319+ (IMAGE_TESTING_FAILED, "Failed"),
320+ (PKG_PUBLISHING_COMPLETED, "Completed"),
321+ (PKG_PUBLISHING_FAILED, "Failed"),
322+ (COMPLETED, "Completed"),
323+ )
324+ owner = models.ForeignKey(Person)
325+ title = models.CharField(max_length=4096)
326+ description = models.TextField()
327+ bug_id = models.IntegerField(null=True, blank=True)
328+ current_workflow_step = models.IntegerField(choices=WORKFLOW_STEPS,
329+ max_length=4096, default=000)
330+ status = models.IntegerField(choices=WORKFLOW_STEP_STATUSES,
331+ max_length=4096, default=000)
332+ base_image = models.CharField(max_length=4096)
333+
334+ def __unicode__(self):
335+ return self.title
336+
337+
338+class SourcePackageUpload(models.Model):
339+ class Meta:
340+ db_table = 'sourcepackageupload'
341+
342+ sourcepackage = models.ForeignKey(SourcePackage)
343+ version = models.CharField(max_length=4096)
344+
345+ def __unicode__(self):
346+ return "{} {}".format(self.sourcepackage, self.version)
347+
348+
349+class SubTicket(models.Model):
350+ class Meta:
351+ db_table = 'subticket'
352+
353+ NEW = 000
354+ QUEUED = 100
355+ PENDING = 110
356+ COMPLETED = 1000
357+
358+ PKG_BUILDING = 200
359+ PKG_BUILDING_WAITING = 210
360+ PKG_BUILDING_INPROGRESS = 220
361+ PKG_BUILDING_COMPLETED = 230
362+ PKG_BUILDING_FAILED = 240
363+
364+ WORKFLOW_STEPS = (
365+ (NEW, "New"),
366+ (QUEUED, "Queued"),
367+ (PKG_BUILDING, "Package building"),
368+ (COMPLETED, "Completed"),
369+ )
370+ WORKFLOW_STEP_STATUSES = (
371+ (NEW, "New"),
372+ (QUEUED, "Queued"),
373+ (PKG_BUILDING_WAITING, "Not started"),
374+ (PKG_BUILDING_INPROGRESS, "In progress"),
375+ (PKG_BUILDING_COMPLETED, "Completed"),
376+ (PKG_BUILDING_FAILED, "Failed"),
377+ )
378+ current_workflow_step = models.IntegerField(choices=WORKFLOW_STEPS,
379+ max_length=4096, default=000)
380+ status = models.IntegerField(choices=WORKFLOW_STEP_STATUSES,
381+ max_length=4096, default=000)
382+ ticket = models.ForeignKey(Ticket)
383+ assignee = models.ForeignKey(Person)
384+ source_package_upload = models.ForeignKey(SourcePackageUpload)
385+
386+ def __unicode__(self):
387+ return "{} {}".format(self.ticket, self.sourcepackageupload)
388+
389+
390+class Artifact(models.Model):
391+ class Meta:
392+ db_table = 'artifact'
393+
394+ ARTIFACT_TYPE = (
395+ ('RESULTS', 'Test Results'),
396+ ('SPU', 'Source Package Upload'),
397+ ('LOGS', 'Logs'),
398+ )
399+ type = models.CharField(choices=ARTIFACT_TYPE, max_length=4096)
400+ ticket_component = models.ForeignKey(SubTicket, blank=True, null=True)
401+ # 'reference' provided by the artifact manager
402+ reference = models.CharField(max_length=4096)
403+ name = models.CharField(max_length=4096)
404+
405+ def __unicode__(self):
406+ return "{} {}".format(self.name, self.type)
407
408=== added file 'ticket_system/ticket/tests.py'
409--- ticket_system/ticket/tests.py 1970-01-01 00:00:00 +0000
410+++ ticket_system/ticket/tests.py 2013-12-11 18:03:47 +0000
411@@ -0,0 +1,131 @@
412+# Ubuntu Continuous Integration Engine
413+# Copyright 2013 Canonical Ltd.
414+
415+# This program is free software: you can redistribute it and/or modify it
416+# under the terms of the GNU Affero General Public License version 3, as
417+# published by the Free Software Foundation.
418+
419+# This program is distributed in the hope that it will be useful, but
420+# WITHOUT ANY WARRANTY; without even the implied warranties of
421+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
422+# PURPOSE. See the GNU Affero General Public License for more details.
423+
424+# You should have received a copy of the GNU Affero General Public License
425+# along with this program. If not, see <http://www.gnu.org/licenses/>.
426+
427+from django.test import TestCase
428+from people.models import Person
429+from project.models import SourcePackage
430+from ticket.models import Ticket, SubTicket, SourcePackageUpload, Artifact
431+
432+
433+def create_person(name='Chris Johnston', email='chrisjohnston@ubuntu.com'):
434+ person = Person()
435+ person.name = name
436+ person.email = email
437+ person.save()
438+ return person
439+
440+
441+def create_sourcepackage(name='my-package'):
442+ sourcepackage = SourcePackage()
443+ sourcepackage.name = name
444+ sourcepackage.save()
445+ return sourcepackage
446+
447+
448+def create_sourcepackageupload(sourcepackage='my-package', version='1.1'):
449+ spu = SourcePackageUpload()
450+ spu.sourcepackage = sourcepackage
451+ spu.version = version
452+ spu.save()
453+ return spu
454+
455+
456+def create_ticket(title="This is my ticket",
457+ description="this is my ticket description",
458+ bug_id='12345', owner='Chris Johnston',
459+ current_workflow_step=Ticket.PKG_BUILDING,
460+ status=Ticket.PKG_BUILDING_INPROGRESS, base_image='17'):
461+ ticket = Ticket()
462+ ticket.owner = owner
463+ ticket.title = title
464+ ticket.description = description
465+ ticket.bug_id = bug_id
466+ ticket.current_workflow_step = current_workflow_step
467+ ticket.status = status
468+ ticket.base_image = base_image
469+ ticket.save()
470+ return ticket
471+
472+
473+def create_subticket(current_workflow_step=SubTicket.PKG_BUILDING, ticket=None,
474+ status=SubTicket.PKG_BUILDING_INPROGRESS,
475+ assignee='Chris Johnston',
476+ source_package_upload=None):
477+ subticket = SubTicket()
478+ subticket.current_workflow_step = current_workflow_step
479+ subticket.status = status
480+ subticket.ticket = ticket
481+ subticket.assignee = assignee
482+ subticket.source_package_upload = source_package_upload
483+ subticket.save()
484+ return subticket
485+
486+
487+def create_artifact(name="test.log", ticket_component=None,
488+ reference='123abc'):
489+ artifact = Artifact()
490+ artifact.name = name
491+ artifact.ticket_component = ticket_component
492+ artifact.reference = reference
493+ artifact.save()
494+ return artifact
495+
496+
497+class TicketModelsTestCase(TestCase):
498+ def setUp(self):
499+ self.person = create_person()
500+ self.sourcepackage = create_sourcepackage()
501+ self.spu = create_sourcepackageupload(
502+ sourcepackage=self.sourcepackage)
503+ self.ticket = create_ticket(owner=self.person)
504+ self.subticket = create_subticket(ticket=self.ticket,
505+ assignee=self.person,
506+ source_package_upload=self.spu)
507+ self.artifact_1 = create_artifact(ticket_component=self.subticket)
508+
509+ def test_creating_an_artifact(self):
510+ artifact_in_database = Artifact.objects.exclude(
511+ ticket_component=None)
512+ self.assertEquals(len(artifact_in_database), 1)
513+ only_artifact_in_database = artifact_in_database[0]
514+ self.assertEquals(only_artifact_in_database, self.artifact_1)
515+
516+ self.assertEquals(only_artifact_in_database.name, "test.log")
517+
518+ def test_creating_a_ticket(self):
519+ ticket_in_database = Ticket.objects.all()
520+ self.assertEquals(len(ticket_in_database), 1)
521+ only_ticket_in_database = ticket_in_database[0]
522+ self.assertEquals(only_ticket_in_database, self.ticket)
523+
524+ self.assertEquals(only_ticket_in_database.title, "This is my ticket")
525+
526+ def test_creating_a_subticket(self):
527+ subticket_in_database = SubTicket.objects.all()
528+ self.assertEquals(len(subticket_in_database), 1)
529+ only_subticket_in_database = subticket_in_database[0]
530+ self.assertEquals(only_subticket_in_database, self.subticket)
531+
532+ self.assertEquals(only_subticket_in_database.assignee.name,
533+ "Chris Johnston")
534+
535+ def test_creating_a_sourcepackageupload(self):
536+ spu_in_database = SourcePackageUpload.objects.all()
537+ self.assertEquals(len(spu_in_database), 1)
538+ only_spu_in_database = spu_in_database[0]
539+ self.assertEquals(only_spu_in_database, self.spu)
540+
541+ self.assertEquals(only_spu_in_database.sourcepackage.name,
542+ "my-package")
543
544=== modified file 'ticket_system/ticket_system/local_tests.py'
545--- ticket_system/ticket_system/local_tests.py 2013-12-09 17:29:57 +0000
546+++ ticket_system/ticket_system/local_tests.py 2013-12-11 18:03:47 +0000
547@@ -1,4 +1,3 @@
548-# Houston
549 # Ubuntu Continuous Integration Engine
550 # Copyright 2013 Canonical Ltd.
551
552
553=== modified file 'ticket_system/ticket_system/settings.py'
554--- ticket_system/ticket_system/settings.py 2013-12-09 19:35:22 +0000
555+++ ticket_system/ticket_system/settings.py 2013-12-11 18:03:47 +0000
556@@ -141,6 +141,7 @@
557 LOCAL_APPS = (
558 'people',
559 'project',
560+ 'ticket',
561 )
562
563 INSTALLED_APPS = LOCAL_APPS + INSTALLED_APPS

Subscribers

People subscribed via source and target branches