]> git.ozlabs.org Git - patchwork/commitdiff
Add unconfirmed registration expiry
authorJeremy Kerr <jk@ozlabs.org>
Tue, 22 Apr 2014 12:08:27 +0000 (20:08 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Tue, 22 Apr 2014 12:13:27 +0000 (20:13 +0800)
Currently, unconfirmed registrations remain in the database. Although we
have an expiry for the registrations, we don't actually remove rows from
the database. This can clog the admin interface up with unnecessary
registration spam.

We currently have a patchwork cron script to send notifications on patch
changes, so hook this into a new do_expiry function.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
apps/patchwork/bin/patchwork-cron.py
apps/patchwork/models.py
apps/patchwork/tests/__init__.py
apps/patchwork/tests/expiry.py [new file with mode: 0644]
apps/patchwork/utils.py

index e9bd0c1cf9f07ff3dba81500bd27bbdaebfc5f21..148e97c764c2177007842c55a407fcba00031f72 100755 (executable)
@@ -1,13 +1,15 @@
 #!/usr/bin/env python
 
 import sys
-from patchwork.utils import send_notifications
+from patchwork.utils import send_notifications, do_expiry
 
 def main(args):
     errors = send_notifications()
     for (recipient, error) in errors:
         print "Failed sending to %s: %s" % (recipient.email, ex)
 
+    do_expiry()
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv))
 
index ec5727d25a862776a6489dcd078cb9166106814e..7371d8fcd1b65ca15887ce5c2f783ad73b648225 100644 (file)
@@ -31,7 +31,8 @@ import random
 class Person(models.Model):
     email = models.CharField(max_length=255, unique = True)
     name = models.CharField(max_length=255, null = True, blank = True)
-    user = models.ForeignKey(User, null = True, blank = True)
+    user = models.ForeignKey(User, null = True, blank = True,
+            on_delete = models.SET_NULL)
 
     def __unicode__(self):
         if self.name:
index e4bf42c27046cb8889ff2aaac90ba8046e2c14c8..5dfcdee27c19343c15612c7711581cf635f760b2 100644 (file)
@@ -30,3 +30,4 @@ from patchwork.tests.mail_settings import *
 from patchwork.tests.notifications import *
 from patchwork.tests.list import *
 from patchwork.tests.person import *
+from patchwork.tests.expiry import *
diff --git a/apps/patchwork/tests/expiry.py b/apps/patchwork/tests/expiry.py
new file mode 100644 (file)
index 0000000..844ed4b
--- /dev/null
@@ -0,0 +1,121 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2014 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
+
+import unittest
+import datetime
+from django.test import TestCase
+from django.contrib.auth.models import User
+from patchwork.models import EmailConfirmation, Person, Patch
+from patchwork.tests.utils import create_user, defaults
+from patchwork.utils import do_expiry
+
+class TestRegistrationExpiry(TestCase):
+
+    def register(self, date):
+        user = create_user()
+        user.is_active = False
+        user.date_joined = user.last_login = date
+        user.save()
+
+        conf = EmailConfirmation(type='registration', user=user,
+                                    email=user.email)
+        conf.date = date
+        conf.save()
+
+        return (user, conf)
+
+    def testOldRegistrationExpiry(self):
+        date = ((datetime.datetime.now() - EmailConfirmation.validity) -
+                datetime.timedelta(hours = 1))
+        (user, conf) = self.register(date)
+
+        do_expiry()
+
+        self.assertFalse(User.objects.filter(pk = user.pk).exists())
+        self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk)
+                            .exists())
+
+
+    def testRecentRegistrationExpiry(self):
+        date = ((datetime.datetime.now() - EmailConfirmation.validity) +
+                datetime.timedelta(hours = 1))
+        (user, conf) = self.register(date)
+
+        do_expiry()
+
+        self.assertTrue(User.objects.filter(pk = user.pk).exists())
+        self.assertTrue(EmailConfirmation.objects.filter(pk = conf.pk)
+                            .exists())
+
+    def testInactiveRegistrationExpiry(self):
+        (user, conf) = self.register(datetime.datetime.now())
+
+        # confirm registration
+        conf.user.is_active = True
+        conf.user.save()
+        conf.deactivate()
+
+        do_expiry()
+
+        self.assertTrue(User.objects.filter(pk = user.pk).exists())
+        self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk)
+                            .exists())
+
+    def testPatchSubmitterExpiry(self):
+        defaults.project.save()
+        defaults.patch_author_person.save()
+
+        # someone submits a patch...
+        patch = Patch(project = defaults.project,
+                    msgid = 'test@example.com', name = 'test patch',
+                    submitter = defaults.patch_author_person,
+                    content = defaults.patch)
+        patch.save()
+
+        # ... then starts registration...
+        date = ((datetime.datetime.now() - EmailConfirmation.validity) -
+                datetime.timedelta(hours = 1))
+        userid = 'test-user'
+        user = User.objects.create_user(userid,
+                defaults.patch_author_person.email, userid)
+        user.is_active = False
+        user.date_joined = user.last_login = date
+        user.save()
+
+        self.assertEqual(user.email, patch.submitter.email)
+
+        conf = EmailConfirmation(type='registration', user=user,
+                                    email=user.email)
+        conf.date = date
+        conf.save()
+
+        # ... which expires
+        do_expiry()
+
+        # we should see no matching user
+        self.assertFalse(User.objects.filter(email = patch.submitter.email)
+                            .exists())
+        # but the patch and person should still be present
+        self.assertTrue(Person.objects.filter(
+                            pk = defaults.patch_author_person.pk).exists())
+        self.assertTrue(Patch.objects.filter(pk = patch.pk).exists())
+
+        # and there should be no user associated with the person
+        self.assertEqual(Person.objects.get(pk =
+                    defaults.patch_author_person.pk).user, None)
index 37b85ce1f503396c3363d2ad3f59c70315471134..9e1702e647673c6295fbe36524946895be83392d 100644 (file)
@@ -22,14 +22,15 @@ import itertools
 import datetime
 from django.shortcuts import get_object_or_404
 from django.template.loader import render_to_string
+from django.contrib.auth.models import User
 from django.contrib.sites.models import Site
 from django.conf import settings
 from django.core.mail import EmailMessage
-from django.db.models import Max
+from django.db.models import Max, Q, F
 from django.db.utils import IntegrityError
 from patchwork.forms import MultiplePatchForm
 from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \
-        PatchChangeNotification, EmailOptout
+        PatchChangeNotification, EmailOptout, EmailConfirmation
 
 def get_patch_ids(d, prefix = 'patch_id'):
     ids = []
@@ -224,3 +225,24 @@ def send_notifications():
         delete_notifications()
 
     return errors
+
+def do_expiry():
+    # expire any pending confirmations
+    q = (Q(date__lt = datetime.datetime.now() - EmailConfirmation.validity) |
+            Q(active = False))
+    EmailConfirmation.objects.filter(q).delete()
+
+    # expire inactive users with no pending confirmation
+    pending_confs = EmailConfirmation.objects.values('user')
+    users = User.objects.filter(
+                is_active = False,
+                last_login = F('date_joined')
+            ).exclude(
+                id__in = pending_confs
+            )
+
+    # delete users
+    users.delete()
+
+
+