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
=== modified file 'ticket_system/people/__init__.py'
--- ticket_system/people/__init__.py 2013-12-09 17:43:12 +0000
+++ ticket_system/people/__init__.py 2013-12-11 18:03:47 +0000
@@ -1,4 +1,3 @@
1# Houston
2# Ubuntu Continuous Integration Engine1# Ubuntu Continuous Integration Engine
3# Copyright 2013 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
43
54
=== modified file 'ticket_system/people/admin.py'
--- ticket_system/people/admin.py 2013-12-09 17:43:12 +0000
+++ ticket_system/people/admin.py 2013-12-11 18:03:47 +0000
@@ -1,4 +1,3 @@
1# Houston
2# Ubuntu Continuous Integration Engine1# Ubuntu Continuous Integration Engine
3# Copyright 2013 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
43
54
=== modified file 'ticket_system/people/api.py'
--- ticket_system/people/api.py 2013-12-09 17:43:12 +0000
+++ ticket_system/people/api.py 2013-12-11 18:03:47 +0000
@@ -1,4 +1,3 @@
1# Houston
2# Ubuntu Continuous Integration Engine1# Ubuntu Continuous Integration Engine
3# Copyright 2013 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
43
54
=== modified file 'ticket_system/people/models.py'
--- ticket_system/people/models.py 2013-12-09 18:57:54 +0000
+++ ticket_system/people/models.py 2013-12-11 18:03:47 +0000
@@ -1,4 +1,3 @@
1# Houston
2# Ubuntu Continuous Integration Engine1# Ubuntu Continuous Integration Engine
3# Copyright 2013 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
43
54
=== modified file 'ticket_system/people/tests.py'
--- ticket_system/people/tests.py 2013-12-10 18:34:08 +0000
+++ ticket_system/people/tests.py 2013-12-11 18:03:47 +0000
@@ -1,4 +1,3 @@
1# Houston
2# Ubuntu Continuous Integration Engine1# Ubuntu Continuous Integration Engine
3# Copyright 2013 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
43
54
=== added directory 'ticket_system/ticket'
=== added file 'ticket_system/ticket/__init__.py'
--- ticket_system/ticket/__init__.py 1970-01-01 00:00:00 +0000
+++ ticket_system/ticket/__init__.py 2013-12-11 18:03:47 +0000
@@ -0,0 +1,14 @@
1# Ubuntu Continuous Integration Engine
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
015
=== added file 'ticket_system/ticket/admin.py'
--- ticket_system/ticket/admin.py 1970-01-01 00:00:00 +0000
+++ ticket_system/ticket/admin.py 2013-12-11 18:03:47 +0000
@@ -0,0 +1,45 @@
1# Ubuntu Continuous Integration Engine
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from django.contrib import admin
17from ticket.models import Ticket, SubTicket, SourcePackageUpload, Artifact
18
19
20class TicketAdmin(admin.ModelAdmin):
21 list_filter = ['current_workflow_step', 'status']
22 search_fields = ['title', 'bug_id']
23 list_display = ('title', 'current_workflow_step', 'status', 'base_image')
24
25
26class SubTicketAdmin(admin.ModelAdmin):
27 list_filter = ['status']
28 search_fields = ['ticket', 'sourcepackageupload']
29 list_display = ('ticket', 'status', 'assignee', 'sourcepackageupload')
30
31
32class SourcePackageUploadAdmin(admin.ModelAdmin):
33 search_fields = ['sourcepackage']
34 list_display = ('sourcepackage', 'version')
35
36
37class ArtifactAdmin(admin.ModelAdmin):
38 list_filter = ['type']
39 search_fields = ['name']
40 list_display = ('name', 'ticket_component', 'type')
41
42admin.site.register(Ticket, TicketAdmin)
43admin.site.register(SubTicket, SubTicketAdmin)
44admin.site.register(SourcePackageUpload, SourcePackageUploadAdmin)
45admin.site.register(Artifact, ArtifactAdmin)
046
=== added directory 'ticket_system/ticket/migrations'
=== added file 'ticket_system/ticket/migrations/0001_initial.py'
--- ticket_system/ticket/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
+++ ticket_system/ticket/migrations/0001_initial.py 2013-12-11 18:03:47 +0000
@@ -0,0 +1,117 @@
1# -*- coding: utf-8 -*-
2import datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding model 'Ticket'
12 db.create_table('ticket', (
13 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['people.Person'])),
15 ('title', self.gf('django.db.models.fields.CharField')(max_length=4096)),
16 ('description', self.gf('django.db.models.fields.TextField')()),
17 ('bug_id', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
18 ('current_workflow_step', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
19 ('status', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
20 ('base_image', self.gf('django.db.models.fields.CharField')(max_length=4096)),
21 ))
22 db.send_create_signal(u'ticket', ['Ticket'])
23
24 # Adding model 'SourcePackageUpload'
25 db.create_table('sourcepackageupload', (
26 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
27 ('sourcepackage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['project.SourcePackage'])),
28 ('version', self.gf('django.db.models.fields.CharField')(max_length=4096)),
29 ))
30 db.send_create_signal(u'ticket', ['SourcePackageUpload'])
31
32 # Adding model 'SubTicket'
33 db.create_table('subticket', (
34 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
35 ('current_workflow_step', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
36 ('status', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4096)),
37 ('ticket', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.Ticket'])),
38 ('assignee', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['people.Person'])),
39 ('source_package_upload', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.SourcePackageUpload'])),
40 ))
41 db.send_create_signal(u'ticket', ['SubTicket'])
42
43 # Adding model 'Artifact'
44 db.create_table('artifact', (
45 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
46 ('type', self.gf('django.db.models.fields.CharField')(max_length=4096)),
47 ('ticket_component', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ticket.SubTicket'], null=True, blank=True)),
48 ('reference', self.gf('django.db.models.fields.CharField')(max_length=4096)),
49 ('name', self.gf('django.db.models.fields.CharField')(max_length=4096)),
50 ))
51 db.send_create_signal(u'ticket', ['Artifact'])
52
53
54 def backwards(self, orm):
55 # Deleting model 'Ticket'
56 db.delete_table('ticket')
57
58 # Deleting model 'SourcePackageUpload'
59 db.delete_table('sourcepackageupload')
60
61 # Deleting model 'SubTicket'
62 db.delete_table('subticket')
63
64 # Deleting model 'Artifact'
65 db.delete_table('artifact')
66
67
68 models = {
69 u'people.person': {
70 'Meta': {'object_name': 'Person', 'db_table': "'person'"},
71 'email': ('django.db.models.fields.EmailField', [], {'max_length': '4096'}),
72 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
73 'is_team': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
74 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
75 },
76 u'project.sourcepackage': {
77 'Meta': {'object_name': 'SourcePackage', 'db_table': "'sourcepackage'"},
78 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
80 },
81 u'ticket.artifact': {
82 'Meta': {'object_name': 'Artifact', 'db_table': "'artifact'"},
83 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
84 'name': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
85 'reference': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
86 'ticket_component': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SubTicket']", 'null': 'True', 'blank': 'True'}),
87 'type': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
88 },
89 u'ticket.sourcepackageupload': {
90 'Meta': {'object_name': 'SourcePackageUpload', 'db_table': "'sourcepackageupload'"},
91 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'sourcepackage': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['project.SourcePackage']"}),
93 'version': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
94 },
95 u'ticket.subticket': {
96 'Meta': {'object_name': 'SubTicket', 'db_table': "'subticket'"},
97 'assignee': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['people.Person']"}),
98 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
99 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 'source_package_upload': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.SourcePackageUpload']"}),
101 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
102 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ticket.Ticket']"})
103 },
104 u'ticket.ticket': {
105 'Meta': {'object_name': 'Ticket', 'db_table': "'ticket'"},
106 'base_image': ('django.db.models.fields.CharField', [], {'max_length': '4096'}),
107 'bug_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
108 'current_workflow_step': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
109 'description': ('django.db.models.fields.TextField', [], {}),
110 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
111 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['people.Person']"}),
112 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4096'}),
113 'title': ('django.db.models.fields.CharField', [], {'max_length': '4096'})
114 }
115 }
116
117 complete_apps = ['ticket']
0\ No newline at end of file118\ No newline at end of file
1119
=== added file 'ticket_system/ticket/migrations/__init__.py'
=== added file 'ticket_system/ticket/models.py'
--- ticket_system/ticket/models.py 1970-01-01 00:00:00 +0000
+++ ticket_system/ticket/models.py 2013-12-11 18:03:47 +0000
@@ -0,0 +1,162 @@
1# Ubuntu Continuous Integration Engine
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from django.db import models
17from people.models import Person
18from project.models import SourcePackage
19
20
21class Ticket(models.Model):
22 class Meta:
23 db_table = 'ticket'
24
25 NEW = 000
26 QUEUED = 100
27 PENDING = 110
28 COMPLETED = 1000
29
30 PKG_BUILDING = 200
31 PKG_BUILDING_WAITING = 210
32 PKG_BUILDING_INPROGRESS = 220
33 PKG_BUILDING_COMPLETED = 230
34 PKG_BUILDING_FAILED = 240
35
36 IMAGE_BUILDING = 300
37 IMAGE_BUILDING_WAITING = 310
38 IMAGE_BUILDING_INPROGRESS = 320
39 IMAGE_BUILDING_COMPLETED = 330
40 IMAGE_BUILDING_FAILED = 340
41
42 IMAGE_TESTING = 400
43 IMAGE_TESTING_WAITING = 410
44 IMAGE_TESTING_INPROGRESS = 420
45 IMAGE_TESTING_PASSED = 430
46 IMAGE_TESTING_FAILED = 440
47
48 PKG_PUBLISHING = 500
49 PKG_PUBLISHING_COMPLETED = 510
50 PKG_PUBLISHING_FAILED = 520
51
52 WORKFLOW_STEPS = (
53 (NEW, "New"),
54 (QUEUED, "Queued"),
55 (PKG_BUILDING, "Package building"),
56 (IMAGE_BUILDING, "Image building"),
57 (IMAGE_TESTING, "Image testing"),
58 (PKG_PUBLISHING, "Package publishing"),
59 (COMPLETED, "Completed"),
60 )
61 WORKFLOW_STEP_STATUSES = (
62 (NEW, "New"),
63 (PENDING, "Not started"),
64 (PKG_BUILDING_WAITING, "Not started"),
65 (PKG_BUILDING_INPROGRESS, "In progress"),
66 (PKG_BUILDING_COMPLETED, "Completed"),
67 (PKG_BUILDING_FAILED, "Failed"),
68 (IMAGE_BUILDING_WAITING, "Not started"),
69 (IMAGE_BUILDING_INPROGRESS, "In progress"),
70 (IMAGE_BUILDING_COMPLETED, "Completed"),
71 (IMAGE_BUILDING_FAILED, "Failed"),
72 (IMAGE_TESTING_WAITING, "Not started"),
73 (IMAGE_TESTING_INPROGRESS, "In progress"),
74 (IMAGE_TESTING_PASSED, "Passed"),
75 (IMAGE_TESTING_FAILED, "Failed"),
76 (PKG_PUBLISHING_COMPLETED, "Completed"),
77 (PKG_PUBLISHING_FAILED, "Failed"),
78 (COMPLETED, "Completed"),
79 )
80 owner = models.ForeignKey(Person)
81 title = models.CharField(max_length=4096)
82 description = models.TextField()
83 bug_id = models.IntegerField(null=True, blank=True)
84 current_workflow_step = models.IntegerField(choices=WORKFLOW_STEPS,
85 max_length=4096, default=000)
86 status = models.IntegerField(choices=WORKFLOW_STEP_STATUSES,
87 max_length=4096, default=000)
88 base_image = models.CharField(max_length=4096)
89
90 def __unicode__(self):
91 return self.title
92
93
94class SourcePackageUpload(models.Model):
95 class Meta:
96 db_table = 'sourcepackageupload'
97
98 sourcepackage = models.ForeignKey(SourcePackage)
99 version = models.CharField(max_length=4096)
100
101 def __unicode__(self):
102 return "{} {}".format(self.sourcepackage, self.version)
103
104
105class SubTicket(models.Model):
106 class Meta:
107 db_table = 'subticket'
108
109 NEW = 000
110 QUEUED = 100
111 PENDING = 110
112 COMPLETED = 1000
113
114 PKG_BUILDING = 200
115 PKG_BUILDING_WAITING = 210
116 PKG_BUILDING_INPROGRESS = 220
117 PKG_BUILDING_COMPLETED = 230
118 PKG_BUILDING_FAILED = 240
119
120 WORKFLOW_STEPS = (
121 (NEW, "New"),
122 (QUEUED, "Queued"),
123 (PKG_BUILDING, "Package building"),
124 (COMPLETED, "Completed"),
125 )
126 WORKFLOW_STEP_STATUSES = (
127 (NEW, "New"),
128 (QUEUED, "Queued"),
129 (PKG_BUILDING_WAITING, "Not started"),
130 (PKG_BUILDING_INPROGRESS, "In progress"),
131 (PKG_BUILDING_COMPLETED, "Completed"),
132 (PKG_BUILDING_FAILED, "Failed"),
133 )
134 current_workflow_step = models.IntegerField(choices=WORKFLOW_STEPS,
135 max_length=4096, default=000)
136 status = models.IntegerField(choices=WORKFLOW_STEP_STATUSES,
137 max_length=4096, default=000)
138 ticket = models.ForeignKey(Ticket)
139 assignee = models.ForeignKey(Person)
140 source_package_upload = models.ForeignKey(SourcePackageUpload)
141
142 def __unicode__(self):
143 return "{} {}".format(self.ticket, self.sourcepackageupload)
144
145
146class Artifact(models.Model):
147 class Meta:
148 db_table = 'artifact'
149
150 ARTIFACT_TYPE = (
151 ('RESULTS', 'Test Results'),
152 ('SPU', 'Source Package Upload'),
153 ('LOGS', 'Logs'),
154 )
155 type = models.CharField(choices=ARTIFACT_TYPE, max_length=4096)
156 ticket_component = models.ForeignKey(SubTicket, blank=True, null=True)
157 # 'reference' provided by the artifact manager
158 reference = models.CharField(max_length=4096)
159 name = models.CharField(max_length=4096)
160
161 def __unicode__(self):
162 return "{} {}".format(self.name, self.type)
0163
=== added file 'ticket_system/ticket/tests.py'
--- ticket_system/ticket/tests.py 1970-01-01 00:00:00 +0000
+++ ticket_system/ticket/tests.py 2013-12-11 18:03:47 +0000
@@ -0,0 +1,131 @@
1# Ubuntu Continuous Integration Engine
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from django.test import TestCase
17from people.models import Person
18from project.models import SourcePackage
19from ticket.models import Ticket, SubTicket, SourcePackageUpload, Artifact
20
21
22def create_person(name='Chris Johnston', email='chrisjohnston@ubuntu.com'):
23 person = Person()
24 person.name = name
25 person.email = email
26 person.save()
27 return person
28
29
30def create_sourcepackage(name='my-package'):
31 sourcepackage = SourcePackage()
32 sourcepackage.name = name
33 sourcepackage.save()
34 return sourcepackage
35
36
37def create_sourcepackageupload(sourcepackage='my-package', version='1.1'):
38 spu = SourcePackageUpload()
39 spu.sourcepackage = sourcepackage
40 spu.version = version
41 spu.save()
42 return spu
43
44
45def create_ticket(title="This is my ticket",
46 description="this is my ticket description",
47 bug_id='12345', owner='Chris Johnston',
48 current_workflow_step=Ticket.PKG_BUILDING,
49 status=Ticket.PKG_BUILDING_INPROGRESS, base_image='17'):
50 ticket = Ticket()
51 ticket.owner = owner
52 ticket.title = title
53 ticket.description = description
54 ticket.bug_id = bug_id
55 ticket.current_workflow_step = current_workflow_step
56 ticket.status = status
57 ticket.base_image = base_image
58 ticket.save()
59 return ticket
60
61
62def create_subticket(current_workflow_step=SubTicket.PKG_BUILDING, ticket=None,
63 status=SubTicket.PKG_BUILDING_INPROGRESS,
64 assignee='Chris Johnston',
65 source_package_upload=None):
66 subticket = SubTicket()
67 subticket.current_workflow_step = current_workflow_step
68 subticket.status = status
69 subticket.ticket = ticket
70 subticket.assignee = assignee
71 subticket.source_package_upload = source_package_upload
72 subticket.save()
73 return subticket
74
75
76def create_artifact(name="test.log", ticket_component=None,
77 reference='123abc'):
78 artifact = Artifact()
79 artifact.name = name
80 artifact.ticket_component = ticket_component
81 artifact.reference = reference
82 artifact.save()
83 return artifact
84
85
86class TicketModelsTestCase(TestCase):
87 def setUp(self):
88 self.person = create_person()
89 self.sourcepackage = create_sourcepackage()
90 self.spu = create_sourcepackageupload(
91 sourcepackage=self.sourcepackage)
92 self.ticket = create_ticket(owner=self.person)
93 self.subticket = create_subticket(ticket=self.ticket,
94 assignee=self.person,
95 source_package_upload=self.spu)
96 self.artifact_1 = create_artifact(ticket_component=self.subticket)
97
98 def test_creating_an_artifact(self):
99 artifact_in_database = Artifact.objects.exclude(
100 ticket_component=None)
101 self.assertEquals(len(artifact_in_database), 1)
102 only_artifact_in_database = artifact_in_database[0]
103 self.assertEquals(only_artifact_in_database, self.artifact_1)
104
105 self.assertEquals(only_artifact_in_database.name, "test.log")
106
107 def test_creating_a_ticket(self):
108 ticket_in_database = Ticket.objects.all()
109 self.assertEquals(len(ticket_in_database), 1)
110 only_ticket_in_database = ticket_in_database[0]
111 self.assertEquals(only_ticket_in_database, self.ticket)
112
113 self.assertEquals(only_ticket_in_database.title, "This is my ticket")
114
115 def test_creating_a_subticket(self):
116 subticket_in_database = SubTicket.objects.all()
117 self.assertEquals(len(subticket_in_database), 1)
118 only_subticket_in_database = subticket_in_database[0]
119 self.assertEquals(only_subticket_in_database, self.subticket)
120
121 self.assertEquals(only_subticket_in_database.assignee.name,
122 "Chris Johnston")
123
124 def test_creating_a_sourcepackageupload(self):
125 spu_in_database = SourcePackageUpload.objects.all()
126 self.assertEquals(len(spu_in_database), 1)
127 only_spu_in_database = spu_in_database[0]
128 self.assertEquals(only_spu_in_database, self.spu)
129
130 self.assertEquals(only_spu_in_database.sourcepackage.name,
131 "my-package")
0132
=== modified file 'ticket_system/ticket_system/local_tests.py'
--- ticket_system/ticket_system/local_tests.py 2013-12-09 17:29:57 +0000
+++ ticket_system/ticket_system/local_tests.py 2013-12-11 18:03:47 +0000
@@ -1,4 +1,3 @@
1# Houston
2# Ubuntu Continuous Integration Engine1# Ubuntu Continuous Integration Engine
3# Copyright 2013 Canonical Ltd.2# Copyright 2013 Canonical Ltd.
43
54
=== modified file 'ticket_system/ticket_system/settings.py'
--- ticket_system/ticket_system/settings.py 2013-12-09 19:35:22 +0000
+++ ticket_system/ticket_system/settings.py 2013-12-11 18:03:47 +0000
@@ -141,6 +141,7 @@
141LOCAL_APPS = (141LOCAL_APPS = (
142 'people',142 'people',
143 'project',143 'project',
144 'ticket',
144)145)
145146
146INSTALLED_APPS = LOCAL_APPS + INSTALLED_APPS147INSTALLED_APPS = LOCAL_APPS + INSTALLED_APPS

Subscribers

People subscribed via source and target branches