Browse Source

Command to send reminders on close data expiration

Send reminders to authors, meant to be called by cron-like.

ref #27
Jocelyn Delalande 7 years ago
parent
commit
2a62e6450f

+ 17 - 0
README.md

@@ -78,6 +78,23 @@ Notification sender address:
 
 
     DEFAULT_FROM_EMAIL='notifier@example.tld'
     DEFAULT_FROM_EMAIL='notifier@example.tld'
 
 
+
+### Data expiration
+
+The data gets deleted after one year, if the contributing user does not give
+its explicit consent to keep it one more year.
+
+Reminders are sent to the contribution author when expiration date gets
+close. By default we send two notifications :
+
+    DATA_EXPIRATION_REMINDERS = [
+        30,  # 1 month before
+        7,   # 1 week before
+    ]
+
+You can tweak it to your will or decide to send no reminder (with value `[]`).
+
+
 Migrate from bottle version (optional)
 Migrate from bottle version (optional)
 ======================================
 ======================================
 
 

+ 0 - 0
wifiwithme/apps/contribmap/management/__init__.py


+ 0 - 0
wifiwithme/apps/contribmap/management/commands/__init__.py


+ 69 - 0
wifiwithme/apps/contribmap/management/commands/send_expiration_reminders.py

@@ -0,0 +1,69 @@
+""" Send reminders for contribution that are about to expire
+
+It offers a way for contributors to opt-in keeping the data one year more (or
+deleting it right now).
+
+Reminders are sent when the script is called exactly `n` days before the
+expiration date.
+
+This `n` can be configured via `DATA_EXPIRATION_REMINDERS` setting.
+"""
+
+from django.conf import settings
+from django.core.mail import send_mail
+from django.core.management.base import BaseCommand, CommandError
+from django.template.loader import get_template
+
+
+from ...models import Contrib
+
+
+class Command(BaseCommand):
+    help = __doc__
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            '--dry-run', default=False, action='store_true',
+            help="Do not actually send emails, just pretend to")
+
+    def handle(self, dry_run, *args, **options):
+        if dry_run:
+            self.stderr.write('DRY RUN MODE: we do not actually send emails.')
+
+        for ndays in settings.DATA_EXPIRATION_REMINDERS:
+            expirating_contribs = Contrib.objects.expires_in_days(ndays)
+            for contrib in expirating_contribs:
+                if not contrib.email:
+                    self._warn_no_email(contrib, ndays)
+                else:
+                    if not dry_run:
+                        self.send_expiration_reminder(contrib, ndays)
+                    self._log_sent_email(contrib, ndays)
+
+    def _warn_no_email(self, contrib, ndays):
+        self.stderr.write(
+            'WARNING : the contribution {} is about to expire in {} days but'
+            + 'do not define a contact email.'.format(
+                contrib, ndays))
+
+    def _log_sent_email(self, contrib, ndays):
+        self.stderr.write(
+            "Sent reminder email to for {} expiration in {} days".format(
+                contrib, ndays))
+
+    def send_expiration_reminder(self, contrib, ndays):
+        subject = get_template(
+            'contribmap/mails/expiration_reminder.subject')
+        body = get_template(
+            'contribmap/mails/expiration_reminder.txt')
+
+        context = {
+            'contrib': contrib,
+            'ndays': ndays,
+        }
+        send_mail(
+            subject.render(context),
+            body.render(context),
+            settings.DEFAULT_FROM_EMAIL,
+            [contrib.email],
+        )

+ 1 - 0
wifiwithme/apps/contribmap/templates/contribmap/mails/expiration_reminder.subject

@@ -0,0 +1 @@
+[wifi-with-me] Votre demande #{{ contrib.id }} expire dans {{ ndays }} jours

+ 15 - 0
wifiwithme/apps/contribmap/templates/contribmap/mails/expiration_reminder.txt

@@ -0,0 +1,15 @@
+Chèr·e {{ contrib.name }},
+
+Vous aviez déposé le {{ contrib.date }} une demande
+
+Votre demande a bien été enregistrée. Elle est en ligne publiquement à l'adresse : <{{ permalink }}>.
+
+Si tout ou partie des informations n'apparaissent pas, c'est que vous avez choisi qu'elles ne soient pas publiques.
+
+Vous pouvez gérer ou supprimer ta demande grace à ce lien privé à conserver :
+
+<{{ management_link }}>
+
+Bien à toi,
+
+Les bénévoles de {{ isp.NAME }}

+ 2 - 2
wifiwithme/apps/contribmap/templates/contribmap/mails/new_contrib_author_notice.txt

@@ -4,10 +4,10 @@ Votre demande a bien été enregistrée. Elle est en ligne publiquement à l'adr
 
 
 Si tout ou partie des informations n'apparaissent pas, c'est que vous avez choisi qu'elles ne soient pas publiques.
 Si tout ou partie des informations n'apparaissent pas, c'est que vous avez choisi qu'elles ne soient pas publiques.
 
 
-Vous pouvez gérer ou supprimer ta demande grace à ce lien privé à conserver :
+Vous pouvez gérer ou supprimer votre demande grace à ce lien privé à conserver :
 
 
 <{{ management_link }}>
 <{{ management_link }}>
 
 
-Bien à toi,
+Bien à vous,
 
 
 Les bénévoles de {{ isp.NAME }}
 Les bénévoles de {{ isp.NAME }}

+ 34 - 0
wifiwithme/apps/contribmap/tests.py

@@ -3,6 +3,7 @@ import json
 import warnings
 import warnings
 
 
 from django.core import mail
 from django.core import mail
+from django.core.management import call_command
 from django.core.signing import BadSignature
 from django.core.signing import BadSignature
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.test import TestCase, Client, override_settings
 from django.test import TestCase, Client, override_settings
@@ -398,3 +399,36 @@ class ContribTokenManagerTests(TestCase):
         self.assertEqual(
         self.assertEqual(
             manager.get_instance_if_allowed(token, contrib.pk),
             manager.get_instance_if_allowed(token, contrib.pk),
             contrib)
             contrib)
+
+
+class TestManagementCommands(TestCase):
+    def setUp(self):
+        contrib = Contrib.objects.create(
+            name='John',
+            email='foo@example.com',
+            contrib_type=Contrib.CONTRIB_CONNECT,
+            latitude=0.5,
+            longitude=0.5,
+        )
+        contrib.expiration_date = datetime.datetime(
+            2010, 10, 10, tzinfo=pytz.utc)
+        contrib.save()
+
+    @override_settings(DATA_EXPIRATION_REMINDERS=[10])
+    def test_send_expiration_reminders(self):
+        # 11 days before (should not send)
+        with freeze_time('29-09-2010', tz_offset=0):
+            call_command('send_expiration_reminders')
+            self.assertEqual(len(mail.outbox), 0)
+
+        # 9 days before (should not send)
+        with freeze_time('01-10-2010', tz_offset=0):
+            call_command('send_expiration_reminders')
+            self.assertEqual(len(mail.outbox), 0)
+
+        # 10 days before (should send)
+        with freeze_time('30-09-2010', tz_offset=0):
+            call_command('send_expiration_reminders', '--dry-run')
+            self.assertEqual(len(mail.outbox), 0)
+            call_command('send_expiration_reminders')
+            self.assertEqual(len(mail.outbox), 1)

+ 8 - 0
wifiwithme/settings/base.py

@@ -137,3 +137,11 @@ STATICFILES_DIRS = [
 NOTIFICATION_EMAILS = []
 NOTIFICATION_EMAILS = []
 
 
 SITE_URL = 'http://example.com/wifi'
 SITE_URL = 'http://example.com/wifi'
+
+
+# Data expiration warning
+
+DATA_EXPIRATION_REMINDERS = [
+    30,  # 1 month before
+    7,   # 1 week before
+]