[PATCH 06/14] series: Add a Series model

Damien Lespiau damien.lespiau at intel.com
Wed Oct 21 08:59:35 AEDT 2015


v2: Add 'order' metadata to revisions as they do have a natural
    ordering.
v3: Add a couple of utility functions to Series and SeriesRevision
v4: Provide a get_absolute_url() method on Series
v5: Add a dump() method to series, handy for debugging
v6: Use 'Series without cover letter' as default series name instead
    of 'Untitled series' to clearly indicated what's hapening to the
    user (Belen Pena)
v7: Squash in the migration (Stephen Finucane)
v8: Add (untested) manual SQL migration paths (Stephen Finucane)

Signed-off-by: Damien Lespiau <damien.lespiau at intel.com>
Acked-by: Stephen Finucane <stephen.finucane at intel.com>
---
 lib/sql/grant-all.mysql.sql          |  6 +++
 lib/sql/grant-all.postgres.sql       | 17 ++++++--
 lib/sql/migration/016-add-series.sql | 23 ++++++++++
 patchwork/migrations/0003_series.py  | 73 +++++++++++++++++++++++++++++++
 patchwork/models.py                  | 85 ++++++++++++++++++++++++++++++++++++
 5 files changed, 201 insertions(+), 3 deletions(-)
 create mode 100644 lib/sql/migration/016-add-series.sql
 create mode 100644 patchwork/migrations/0003_series.py

diff --git a/lib/sql/grant-all.mysql.sql b/lib/sql/grant-all.mysql.sql
index 6a3d547..fa6128f 100644
--- a/lib/sql/grant-all.mysql.sql
+++ b/lib/sql/grant-all.mysql.sql
@@ -25,6 +25,9 @@ GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_emailoptout TO 'www-data'@loca
 GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_patchchangenotification TO 'www-data'@localhost;
 GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_tag TO 'www-data'@localhost;
 GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_patchtag TO 'www-data'@localhost;
+GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_series TO 'www-data'@localhost;
+GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_seriesrevision TO 'www-data'@localhost;
+GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_seriesrevisionpatch TO 'www-data'@localhost;
 
 -- allow the mail user (in this case, 'nobody') to add patches
 GRANT INSERT, SELECT ON patchwork_patch TO 'nobody'@localhost;
@@ -34,6 +37,9 @@ GRANT INSERT, SELECT, UPDATE, DELETE ON patchwork_patchtag TO 'nobody'@localhost
 GRANT SELECT ON	patchwork_project TO 'nobody'@localhost;
 GRANT SELECT ON patchwork_state TO 'nobody'@localhost;
 GRANT SELECT ON patchwork_tag TO 'nobody'@localhost;
+GRANT INSERT, SELECT, UPDATE ON patchwork_series TO 'nobody'@localhost;
+GRANT INSERT, SELECT ON patchwork_seriesrevision TO 'nobody'@localhost;
+GRANT INSERT, SELECT ON patchwork_seriesrevisionpatch TO 'nobody'@localhost;
 
 COMMIT;
 
diff --git a/lib/sql/grant-all.postgres.sql b/lib/sql/grant-all.postgres.sql
index 477e10a..ff72c7f 100644
--- a/lib/sql/grant-all.postgres.sql
+++ b/lib/sql/grant-all.postgres.sql
@@ -25,7 +25,10 @@ GRANT SELECT, UPDATE, INSERT, DELETE ON
 	patchwork_emailoptout,
 	patchwork_patchchangenotification,
 	patchwork_tag,
-	patchwork_patchtag
+	patchwork_patchtag,
+        patchwork_series,
+        patchwork_seriesrevision,
+        patchwork_seriesrevisionpatch
 TO "www-data";
 GRANT SELECT, UPDATE ON
 	auth_group_id_seq,
@@ -48,14 +51,22 @@ GRANT SELECT, UPDATE ON
 	patchwork_userprofile_id_seq,
 	patchwork_userprofile_maintainer_projects_id_seq,
 	patchwork_tag_id_seq,
-	patchwork_patchtag_id_seq
+	patchwork_patchtag_id_seq,
+        patchwork_series_id_seq,
+        patchwork_seriesrevision_id_seq,
+        patchwork_seriesrevisionpatch_id_seq
 TO "www-data";
 
 -- allow the mail user (in this case, 'nobody') to add patches
 GRANT INSERT, SELECT ON
 	patchwork_patch,
 	patchwork_comment,
-	patchwork_person
+	patchwork_person,
+	patchwork_seriesrevision,
+	patchwork_seriesrevisionpatch
+TO "nobody";
+GRANT INSERT, SELECT, UPDATE ON
+	patchwork_series
 TO "nobody";
 GRANT INSERT, SELECT, UPDATE, DELETE ON
 	patchwork_patchtag
diff --git a/lib/sql/migration/016-add-series.sql b/lib/sql/migration/016-add-series.sql
new file mode 100644
index 0000000..de01be1
--- /dev/null
+++ b/lib/sql/migration/016-add-series.sql
@@ -0,0 +1,23 @@
+BEGIN;
+CREATE TABLE "patchwork_series" ("id" serial NOT NULL PRIMARY KEY, "name" varchar(200) NOT NULL, "submitted" timestamp with time zone NOT NULL, "last_updated" timestamp with time zone NOT NULL, "version" integer NOT NULL, "n_patches" integer NOT NULL, "project_id" integer NOT NULL, "reviewer_id" integer NULL, "submitter_id" integer NOT NULL);
+CREATE TABLE "patchwork_seriesrevision" ("id" serial NOT NULL PRIMARY KEY, "version" integer NOT NULL, "root_msgid" varchar(255) NOT NULL, "cover_letter" text NULL);
+CREATE TABLE "patchwork_seriesrevisionpatch" ("id" serial NOT NULL PRIMARY KEY, "order" integer NOT NULL, "patch_id" integer NOT NULL, "revision_id" integer NOT NULL);
+ALTER TABLE "patchwork_seriesrevision" ADD COLUMN "series_id" integer NOT NULL;
+ALTER TABLE "patchwork_seriesrevision" ALTER COLUMN "series_id" DROP DEFAULT;
+ALTER TABLE "patchwork_seriesrevisionpatch" ADD CONSTRAINT "patchwork_seriesrevisionpatch_revision_id_1109258846b6e0ac_uniq" UNIQUE ("revision_id", "order");
+ALTER TABLE "patchwork_seriesrevisionpatch" ADD CONSTRAINT "patchwork_seriesrevisionpatch_revision_id_6908277fcf2dea5b_uniq" UNIQUE ("revision_id", "patch_id");
+ALTER TABLE "patchwork_seriesrevision" ADD CONSTRAINT "patchwork_seriesrevision_series_id_7548e7889275a653_uniq" UNIQUE ("series_id", "version");
+ALTER TABLE "patchwork_series" ADD CONSTRAINT "patchwork_se_project_id_86fd55162c0ef1b_fk_patchwork_project_id" FOREIGN KEY ("project_id") REFERENCES "patchwork_project" ("id") DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE "patchwork_series" ADD CONSTRAINT "patchwork_series_reviewer_id_42567a6911b21fc8_fk_auth_user_id" FOREIGN KEY ("reviewer_id") REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE "patchwork_series" ADD CONSTRAINT "patchwork_s_submitter_id_8055a5db31fae17_fk_patchwork_person_id" FOREIGN KEY ("submitter_id") REFERENCES "patchwork_person" ("id") DEFERRABLE INITIALLY DEFERRED;
+CREATE INDEX "patchwork_series_b098ad43" ON "patchwork_series" ("project_id");
+CREATE INDEX "patchwork_series_071d8141" ON "patchwork_series" ("reviewer_id");
+CREATE INDEX "patchwork_series_a8919bbb" ON "patchwork_series" ("submitter_id");
+ALTER TABLE "patchwork_seriesrevisionpatch" ADD CONSTRAINT "patchwork_serie_patch_id_75fd1ec59eecf9e6_fk_patchwork_patch_id" FOREIGN KEY ("patch_id") REFERENCES "patchwork_patch" ("id") DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE "patchwork_seriesrevisionpatch" ADD CONSTRAINT "pat_revision_id_3227811f80e535ce_fk_patchwork_seriesrevision_id" FOREIGN KEY ("revision_id") REFERENCES "patchwork_seriesrevision" ("id") DEFERRABLE INITIALLY DEFERRED;
+CREATE INDEX "patchwork_seriesrevisionpatch_f70d6c4e" ON "patchwork_seriesrevisionpatch" ("patch_id");
+CREATE INDEX "patchwork_seriesrevisionpatch_5de09a8d" ON "patchwork_seriesrevisionpatch" ("revision_id");
+CREATE INDEX "patchwork_seriesrevision_a08cee2d" ON "patchwork_seriesrevision" ("series_id");
+ALTER TABLE "patchwork_seriesrevision" ADD CONSTRAINT "patchwork_ser_series_id_2b977316803e35df_fk_patchwork_series_id" FOREIGN KEY ("series_id") REFERENCES "patchwork_series" ("id") DEFERRABLE INITIALLY DEFERRED;
+
+COMMIT;
diff --git a/patchwork/migrations/0003_series.py b/patchwork/migrations/0003_series.py
new file mode 100644
index 0000000..4e5e5dd
--- /dev/null
+++ b/patchwork/migrations/0003_series.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import datetime
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('patchwork', '0002_fix_patch_state_default_values'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Series',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('name', models.CharField(default=b'Series without cover letter', max_length=200)),
+                ('submitted', models.DateTimeField(default=datetime.datetime.now)),
+                ('last_updated', models.DateTimeField(auto_now=True)),
+                ('version', models.IntegerField(default=1)),
+                ('n_patches', models.IntegerField(default=0)),
+                ('project', models.ForeignKey(to='patchwork.Project')),
+                ('reviewer', models.ForeignKey(related_name='reviewers', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
+                ('submitter', models.ForeignKey(related_name='submitters', to='patchwork.Person')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='SeriesRevision',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('version', models.IntegerField(default=1)),
+                ('root_msgid', models.CharField(max_length=255)),
+                ('cover_letter', models.TextField(null=True, blank=True)),
+            ],
+            options={
+                'ordering': ['version'],
+            },
+        ),
+        migrations.CreateModel(
+            name='SeriesRevisionPatch',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('order', models.IntegerField()),
+                ('patch', models.ForeignKey(to='patchwork.Patch')),
+                ('revision', models.ForeignKey(to='patchwork.SeriesRevision')),
+            ],
+            options={
+                'ordering': ['order'],
+            },
+        ),
+        migrations.AddField(
+            model_name='seriesrevision',
+            name='patches',
+            field=models.ManyToManyField(to='patchwork.Patch', through='patchwork.SeriesRevisionPatch'),
+        ),
+        migrations.AddField(
+            model_name='seriesrevision',
+            name='series',
+            field=models.ForeignKey(to='patchwork.Series'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='seriesrevisionpatch',
+            unique_together=set([('revision', 'order'), ('revision', 'patch')]),
+        ),
+        migrations.AlterUniqueTogether(
+            name='seriesrevision',
+            unique_together=set([('series', 'version')]),
+        ),
+    ]
diff --git a/patchwork/models.py b/patchwork/models.py
index a1c2974..feb82c0 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -410,6 +410,91 @@ class BundlePatch(models.Model):
         unique_together = [('bundle', 'patch')]
         ordering = ['order']
 
+SERIES_DEFAULT_NAME = "Series without cover letter"
+
+# This Model represents the "top level" Series, an object that doesn't change
+# with the various versions of patches sent to the mailing list.
+class Series(models.Model):
+    project = models.ForeignKey(Project)
+    name = models.CharField(max_length=200, default=SERIES_DEFAULT_NAME)
+    submitter = models.ForeignKey(Person, related_name='submitters')
+    reviewer = models.ForeignKey(User, related_name='reviewers', null=True,
+                                 blank=True)
+    submitted = models.DateTimeField(default=datetime.datetime.now)
+    last_updated = models.DateTimeField(auto_now=True)
+    # Caches the latest version so we can display it without looking at the max
+    # of all SeriesRevision.version
+    version = models.IntegerField(default=1)
+    # This is the number of patches of the latest version.
+    n_patches = models.IntegerField(default=0)
+
+    def __unicode__(self):
+        return self.name
+
+    def revisions(self):
+        return SeriesRevision.objects.filter(series=self)
+
+    def latest_revision(self):
+        return self.revisions().reverse()[0]
+
+    def get_absolute_url(self):
+        return reverse('series', kwargs={ 'series': self.pk })
+
+    def dump(self):
+        print('')
+        print('===')
+        print('Series: %s' % self)
+        print('    version %d' % self.version)
+        for rev in self.revisions():
+            print('    rev %d:' % rev.version)
+            i = 1
+            for patch in rev.ordered_patches():
+                print('        patch %d:' % i)
+                print('            subject: %s' % patch.name)
+                print('            msgid  : %s' % patch.msgid)
+                i += 1
+
+# A 'revision' of a series. Resending a new version of a patch or a full new
+# iteration of a series will create a new revision.
+class SeriesRevision(models.Model):
+    series = models.ForeignKey(Series)
+    version = models.IntegerField(default=1)
+    root_msgid = models.CharField(max_length=255)
+    cover_letter = models.TextField(null = True, blank = True)
+    patches = models.ManyToManyField(Patch, through = 'SeriesRevisionPatch')
+
+    class Meta:
+        unique_together = [('series', 'version')]
+        ordering = ['version']
+
+    def ordered_patches(self):
+        return self.patches.order_by('seriesrevisionpatch__order')
+
+    def add_patch(self, patch, order):
+        # see if the patch is already in this revision
+        if SeriesRevisionPatch.objects.filter(revision=self,
+                                              patch=patch).count():
+            raise Exception("patch is already in revision")
+
+        sp = SeriesRevisionPatch.objects.create(revision=self, patch=patch,
+                                                order=order)
+        sp.save()
+
+    def __unicode__(self):
+        if hasattr(self, 'series'):
+            return self.series.name + " (rev " + str(self.version) + ")"
+        else:
+            return "New revision" + " (rev " + str(self.version) + ")"
+
+class SeriesRevisionPatch(models.Model):
+    patch = models.ForeignKey(Patch)
+    revision = models.ForeignKey(SeriesRevision)
+    order = models.IntegerField()
+
+    class Meta:
+        unique_together = [('revision', 'patch'), ('revision', 'order')]
+        ordering = ['order']
+
 class EmailConfirmation(models.Model):
     validity = datetime.timedelta(days = settings.CONFIRMATION_VALIDITY_DAYS)
     type = models.CharField(max_length = 20, choices = [
-- 
2.4.3



More information about the Patchwork mailing list