]> git.ozlabs.org Git - patchwork/commitdiff
models: Add PatchChangeNotification and record patch state changes
authorJeremy Kerr <jk@ozlabs.org>
Tue, 29 Mar 2011 03:58:39 +0000 (11:58 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Thu, 14 Apr 2011 09:24:03 +0000 (17:24 +0800)
Add a PatchChangeNotification model to keep track of changes to a
patch's state. Hook this up to Patch's pre_save signal.

Requires a DB schema upgrade.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
apps/patchwork/models.py
apps/patchwork/tests/__init__.py
apps/patchwork/tests/notifications.py [new file with mode: 0644]
lib/sql/grant-all.mysql.sql
lib/sql/grant-all.postgres.sql
lib/sql/migration/011-patch-change-notifications.sql [new file with mode: 0644]

index f21d07322c544d3860a3128f65f76a7faa12b880..17a68db8077ea6332e53aba419cca2e024c41a2c 100644 (file)
@@ -64,6 +64,7 @@ class Project(models.Model):
     name = models.CharField(max_length=255, unique=True)
     listid = models.CharField(max_length=255, unique=True)
     listemail = models.CharField(max_length=200)
+    send_notifications = models.BooleanField()
 
     def __unicode__(self):
         return self.name
@@ -406,3 +407,46 @@ class EmailOptout(models.Model):
 
     def __unicode__(self):
         return self.email
+
+class PatchChangeNotification(models.Model):
+    patch = models.ForeignKey(Patch, primary_key = True)
+    last_modified = models.DateTimeField(default = datetime.datetime.now)
+    orig_state = models.ForeignKey(State)
+
+def _patch_change_callback(sender, instance, **kwargs):
+    # we only want notification of modified patches
+    if instance.pk is None:
+        return
+
+    if instance.project is None or not instance.project.send_notifications:
+        return
+
+    try:
+        orig_patch = Patch.objects.get(pk = instance.pk)
+    except Patch.DoesNotExist:
+        return
+
+    # If there's no interesting changes, abort without creating the
+    # notification
+    if orig_patch.state == instance.state:
+        return
+
+    notification = None
+    try:
+        notification = PatchChangeNotification.objects.get(patch = instance)
+    except PatchChangeNotification.DoesNotExist:
+        pass
+
+    if notification is None:
+        notification = PatchChangeNotification(patch = instance,
+                                               orig_state = orig_patch.state)
+
+    elif notification.orig_state == instance.state:
+        # If we're back at the original state, there is no need to notify
+        notification.delete()
+        return
+
+    notification.last_modified = datetime.datetime.now()
+    notification.save()
+
+models.signals.pre_save.connect(_patch_change_callback, sender = Patch)
index 0b56fc1fc561dd55b75f60fee87f1049b6fb32f9..8ae271ae0871c51a16f16a1df46f5ad66e00ff50 100644 (file)
@@ -27,3 +27,4 @@ from patchwork.tests.confirm import *
 from patchwork.tests.registration import *
 from patchwork.tests.user import *
 from patchwork.tests.mail_settings import *
+from patchwork.tests.notifications import *
diff --git a/apps/patchwork/tests/notifications.py b/apps/patchwork/tests/notifications.py
new file mode 100644 (file)
index 0000000..c4df1b0
--- /dev/null
@@ -0,0 +1,117 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from django.db.utils import IntegrityError
+from patchwork.models import Patch, State, PatchChangeNotification
+from patchwork.tests.utils import defaults, create_maintainer
+
+class PatchNotificationModelTest(TestCase):
+    """Tests for the creation & update of the PatchChangeNotification model"""
+
+    def setUp(self):
+        self.project = defaults.project
+        self.project.send_notifications = True
+        self.project.save()
+        self.submitter = defaults.patch_author_person
+        self.submitter.save()
+        self.patch = Patch(project = self.project, msgid = 'testpatch',
+                        name = 'testpatch', content = '',
+                        submitter = self.submitter)
+
+    def tearDown(self):
+        self.patch.delete()
+        self.submitter.delete()
+        self.project.delete()
+
+    def testPatchCreation(self):
+        """Ensure we don't get a notification on create"""
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+    def testPatchUninterestingChange(self):
+        """Ensure we don't get a notification for "uninteresting" changes"""
+        self.patch.save()
+        self.patch.archived = True
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+    def testPatchChange(self):
+        """Ensure we get a notification for interesting patch changes"""
+        self.patch.save()
+        oldstate = self.patch.state
+        state = State.objects.exclude(pk = oldstate.pk)[0]
+
+        self.patch.state = state
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+        notification = PatchChangeNotification.objects.all()[0]
+        self.assertEqual(notification.patch, self.patch)
+        self.assertEqual(notification.orig_state, oldstate)
+
+    def testNotificationCancelled(self):
+        """Ensure we cancel notifications that are no longer valid"""
+        self.patch.save()
+        oldstate = self.patch.state
+        state = State.objects.exclude(pk = oldstate.pk)[0]
+
+        self.patch.state = state
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+
+        self.patch.state = oldstate
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
+    def testNotificationUpdated(self):
+        """Ensure we update notifications when the patch has a second change,
+           but keep the original patch details"""
+        self.patch.save()
+        oldstate = self.patch.state
+        newstates = State.objects.exclude(pk = oldstate.pk)[:2]
+
+        self.patch.state = newstates[0]
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+        notification = PatchChangeNotification.objects.all()[0]
+        self.assertEqual(notification.orig_state, oldstate)
+        orig_timestamp = notification.last_modified
+                         
+        self.patch.state = newstates[1]
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 1)
+        notification = PatchChangeNotification.objects.all()[0]
+        self.assertEqual(notification.orig_state, oldstate)
+        self.assertTrue(notification.last_modified > orig_timestamp)
+
+    def testProjectNotificationsDisabled(self):
+        """Ensure we don't see notifications created when a project is
+           configured not to send them"""
+        self.project.send_notifications = False
+        self.project.save()
+
+        self.patch.save()
+        oldstate = self.patch.state
+        state = State.objects.exclude(pk = oldstate.pk)[0]
+
+        self.patch.state = state
+        self.patch.save()
+        self.assertEqual(PatchChangeNotification.objects.count(), 0)
+
index c272e1e176227bc166d02177721eb36ca45c8a10..1bff526f44d20ec750cdb80b5aaa0cdd8c16978c 100644 (file)
@@ -23,6 +23,7 @@ GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_bundle TO 'www-data'@localhost
 GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_bundle_patches TO 'www-data'@localhost;
 GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_patch TO 'www-data'@localhost;
 GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_emailoptout TO 'www-data'@localhost;
+GRANT SELECT, UPDATE, INSERT, DELETE ON patchwork_patchchangenotification TO 'www-data'@localhost;
 
 -- allow the mail user (in this case, 'nobody') to add patches
 GRANT INSERT, SELECT ON patchwork_patch TO 'nobody'@localhost;
index 9b6c862bd6ef7f1177ae7e834e00f68b193065fd..72abb570e017150195798d5076d9523917ed1a5d 100644 (file)
@@ -23,7 +23,8 @@ GRANT SELECT, UPDATE, INSERT, DELETE ON
        patchwork_bundle,
        patchwork_bundlepatch,
        patchwork_patch,
-       patchwork_emailoptout
+       patchwork_emailoptout,
+       patchwork_patchchangenotification
 TO "www-data";
 GRANT SELECT, UPDATE ON
        auth_group_id_seq,
@@ -45,7 +46,8 @@ GRANT SELECT, UPDATE ON
        patchwork_state_id_seq,
        patchwork_emailconfirmation_id_seq,
        patchwork_userprofile_id_seq,
-       patchwork_userprofile_maintainer_projects_id_seq
+       patchwork_userprofile_maintainer_projects_id_seq,
+       patchwork_patchchangenotification_id_seq
 TO "www-data";
 
 -- allow the mail user (in this case, 'nobody') to add patches
diff --git a/lib/sql/migration/011-patch-change-notifications.sql b/lib/sql/migration/011-patch-change-notifications.sql
new file mode 100644 (file)
index 0000000..0a9b9b7
--- /dev/null
@@ -0,0 +1,12 @@
+BEGIN;
+CREATE TABLE "patchwork_patchchangenotification" (
+    "patch_id" integer NOT NULL PRIMARY KEY REFERENCES "patchwork_patch" ("id") DEFERRABLE INITIALLY DEFERRED,
+    "last_modified" timestamp with time zone NOT NULL,
+    "orig_state_id" integer NOT NULL REFERENCES "patchwork_state" ("id") DEFERRABLE INITIALLY DEFERRED
+)
+;
+ALTER TABLE "patchwork_project" ADD COLUMN
+    "send_notifications" boolean NOT NULL DEFAULT False;
+ALTER TABLE "patchwork_project" ALTER COLUMN
+    "send_notifications" DROP DEFAULT;
+COMMIT;