summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/patchwork/models.py44
-rw-r--r--apps/patchwork/tests/__init__.py1
-rw-r--r--apps/patchwork/tests/notifications.py117
-rw-r--r--lib/sql/grant-all.mysql.sql1
-rw-r--r--lib/sql/grant-all.postgres.sql6
-rw-r--r--lib/sql/migration/011-patch-change-notifications.sql12
6 files changed, 179 insertions, 2 deletions
diff --git a/apps/patchwork/models.py b/apps/patchwork/models.py
index f21d073..17a68db 100644
--- a/apps/patchwork/models.py
+++ b/apps/patchwork/models.py
@@ -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)
diff --git a/apps/patchwork/tests/__init__.py b/apps/patchwork/tests/__init__.py
index 0b56fc1..8ae271a 100644
--- a/apps/patchwork/tests/__init__.py
+++ b/apps/patchwork/tests/__init__.py
@@ -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
index 0000000..c4df1b0
--- /dev/null
+++ b/apps/patchwork/tests/notifications.py
@@ -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)
+
diff --git a/lib/sql/grant-all.mysql.sql b/lib/sql/grant-all.mysql.sql
index c272e1e..1bff526 100644
--- a/lib/sql/grant-all.mysql.sql
+++ b/lib/sql/grant-all.mysql.sql
@@ -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;
diff --git a/lib/sql/grant-all.postgres.sql b/lib/sql/grant-all.postgres.sql
index 9b6c862..72abb57 100644
--- a/lib/sql/grant-all.postgres.sql
+++ b/lib/sql/grant-all.postgres.sql
@@ -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
index 0000000..0a9b9b7
--- /dev/null
+++ b/lib/sql/migration/011-patch-change-notifications.sql
@@ -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;