Skip to content

Commit

Permalink
Add management command to notify users
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Etienne committed Apr 18, 2016
1 parent db37173 commit 5dee891
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 1 deletion.
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ colour-runner==0.0.4
coverage==4.0.3
dj-database-url==0.4.1
django==1.8.12
factory-boy==2.6.1
flake8==2.5.4
flake8-import-order==0.7
incuna-pigeon==0.1.0
python-dateutil==2.5.2
12 changes: 12 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import factory

from .models import User


class UserFactory(factory.DjangoModelFactory):
name = factory.Sequence('User {}'.format)
username = factory.Sequence('username{}'.format)
email = factory.Sequence('email{}@incuna.com'.format)

class Meta:
model = User
18 changes: 18 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib.auth.models import AbstractUser
from django.db import models

from user_deletion.managers import UserManagerMixin


class UserManager(UserManagerMixin, models.Manager):
pass


class User(AbstractUser):
name = models.CharField(max_length=255)
notified = models.BooleanField(default=False)

objects = UserManager()

def __str__(self):
return self.name
13 changes: 12 additions & 1 deletion tests/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,19 @@
default='sqlite://{}/user_deletion.db'.format(BASEDIR),
),
},
INSTALLED_APPS=('user_deletion',),
INSTALLED_APPS=(
'tests',
'user_deletion',

'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
),
MIDDLEWARE_CLASSES=(),

AUTH_USER_MODEL='tests.User',
SITE_ID=1,
DEFAULT_FROM_EMAIL='[email protected]',
)


Expand Down
43 changes: 43 additions & 0 deletions tests/test_management_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from datetime import datetime
from io import StringIO

from dateutil.relativedelta import relativedelta
from django.core import mail
from django.core.management import call_command
from django.test import TestCase

from .factories import UserFactory


class TestUserNotifyManagementCommand(TestCase):
def setUp(self):
self.stdout = StringIO()

def test_no_users(self):
call_command('notify_users')
self.assertFalse(len(mail.outbox))

def test_inactive_users(self):
year_ago = datetime.now() + relativedelta(months=-12)
UserFactory.create(last_login=year_ago)

call_command('notify_users')

self.assertEqual(len(mail.outbox), 1)

def test_email(self):
year_ago = datetime.now() + relativedelta(months=-12)
UserFactory.create(last_login=year_ago)
call_command('notify_users')

email = mail.outbox[0]
self.assertEqual(email.subject, 'Re-activate your account')
self.assertIn('We have noticed', email.body)

def test_active_users(self):
# user was notified before
year_ago = datetime.now() + relativedelta(months=-13)
UserFactory.create(last_login=year_ago, notified=True)

call_command('notify_users')
self.assertFalse(len(mail.outbox))
20 changes: 20 additions & 0 deletions tests/test_managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from dateutil.relativedelta import relativedelta
from django.apps import apps
from django.test import TestCase
from django.utils import timezone

from .factories import UserFactory
from .models import User


user_deletion_config = apps.get_app_config('user_deletion')


class TestUserDeletionManager(TestCase):
def test_users_to_notify(self):
last_login = timezone.now() - relativedelta(months=user_deletion_config.MONTHS)
user = UserFactory.create(last_login=last_login)

users = User.objects.users_to_notify()

self.assertCountEqual(users, [user])
1 change: 1 addition & 0 deletions user_deletion/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'user_deletion.apps.UserDeletionConfig'
7 changes: 7 additions & 0 deletions user_deletion/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class UserDeletionConfig(AppConfig):
name = 'user_deletion'

MONTHS = 12
Empty file.
Empty file.
17 changes: 17 additions & 0 deletions user_deletion/management/commands/notify_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils import translation

from ...notifications import DeletionNotification

User = get_user_model()


class Command(BaseCommand):
def handle(self, *args, **options):
translation.activate(settings.LANGUAGE_CODE)
users = User.objects.users_to_notify()
site = Site.objects.get_current()
DeletionNotification(user=None, site=site, users=users).notify()
13 changes: 13 additions & 0 deletions user_deletion/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from dateutil.relativedelta import relativedelta
from django.apps import apps
from django.utils import timezone


user_deletion_config = apps.get_app_config('user_deletion')


class UserManagerMixin:
def users_to_notify(self):
"""Finds all users who have been inactive for `MONTHS` and not yet notified."""
last_login = timezone.now() - relativedelta(months=user_deletion_config.MONTHS)
return self.filter(last_login__lte=last_login, notified=False)
25 changes: 25 additions & 0 deletions user_deletion/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.conf import settings
from django.core.mail import send_mass_mail
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from pigeon.notification import Notification


def send_emails(notification):
messages = []
context = {'site': notification.site}
for user in notification.users:
message = render_to_string(notification.template_name, context)
messages.append([
notification.subject,
message,
settings.DEFAULT_FROM_EMAIL,
[user.email],
])
send_mass_mail(messages)


class DeletionNotification(Notification):
handlers = (send_emails,)
template_name = 'user_deletion/email.txt'
subject = _('Re-activate your account')
7 changes: 7 additions & 0 deletions user_deletion/templates/user_deletion/email.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% load i18n %}
{% blocktrans %}
We have noticed that your account has been inactive for over 12 months now.

Please log in to your account in order to keep it alive, otherwise it will be
deleted within the next four weeks.
{% endblocktrans %}

0 comments on commit 5dee891

Please sign in to comment.