Commit 011ee687 authored by Jeremy Kerr's avatar Jeremy Kerr
Browse files

Add unconfirmed registration expiry



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: default avatarJeremy Kerr <jk@ozlabs.org>
parent e4c13aee
#!/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))
......@@ -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:
......
......@@ -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 *
# 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)
......@@ -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()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment