Support automated speaker reminders
authorMagnus Hagander <magnus@hagander.net>
Mon, 15 Oct 2018 17:06:48 +0000 (19:06 +0200)
committerMagnus Hagander <magnus@hagander.net>
Mon, 15 Oct 2018 17:06:48 +0000 (19:06 +0200)
10-15 minutes before a presentation, a reminder is sent to registered
speakers, letting them know that their presentation is coming up, and
which room it's scheduled in.

Currently reminders are only sent over Twitter Direct Message, but the
system is set up so that other integrations can be provided in the
future.

docs/confreg/integrations.md
postgresqleu/confreg/backendforms.py
postgresqleu/confreg/management/commands/confreg_frequent_reminders.py [new file with mode: 0644]
postgresqleu/confreg/migrations/0033_speaker_reminders.py [new file with mode: 0644]
postgresqleu/confreg/models.py

index 7bd72dd57267afcdc12e7d3dcf0df359c9aae6d2..4f9c7798bfdfc859c7c59951ea70b3f63a7a2f0a 100644 (file)
@@ -9,6 +9,7 @@ The twitter integration supports:
 
 * Posting conference news
 * Posting confirmed sponsorship benefits
+* Sending reminders to speaker just before their presentation
 
 ### Posting conference news
 
@@ -28,6 +29,17 @@ will be generated with that template and sent. Normally this is used
 for things like "when the logo benefit is confirmed, post a welcome
 tweet", but it can be used for any defined benefit.
 
+### Sending reminders to speaker just before their presentation
+
+If enabled, each speaker that have given their twitter username during
+registration (regular registration, not call for papers) will get a
+twitter DM sent between 10 and 15 minutes before their presentation
+(depending on cronjob time) reminding them that their presentation
+will begin soon, and which room it's in.
+
+This of course requires that the speaker follows the conference
+account, or that they have public DMs open.
+
 ### Setting up
 
 To set up the twitter integration, first configure `TWITTER_CLIENT`
index 6e7385ba0121b6684eb9188dad04ad6350932a33..151164c6c410b20eb49a2c23c508502abe233427 100644 (file)
@@ -775,7 +775,7 @@ class BackendCopySelectConferenceForm(django.forms.Form):
 class TwitterForm(ConcurrentProtectedModelForm):
        class Meta:
                model = Conference
-               fields = ['twittersync_active', ]
+               fields = ['twittersync_active', 'twitterreminders_active']
 
 class TwitterTestForm(django.forms.Form):
        recipient = django.forms.CharField(max_length=64)
diff --git a/postgresqleu/confreg/management/commands/confreg_frequent_reminders.py b/postgresqleu/confreg/management/commands/confreg_frequent_reminders.py
new file mode 100644 (file)
index 0000000..3533d0d
--- /dev/null
@@ -0,0 +1,68 @@
+#
+# Send frequent reminders using interfaces like twitter DMs
+#
+# For now this only means sending a reminder to speakers 10-15 minutes
+# before their session begins.
+#
+# Intended to run every 2-3 minutes from cron.
+
+from django.core.management.base import BaseCommand
+from django.db import transaction, connection
+from django.conf import settings
+
+from datetime import datetime, timedelta
+
+from postgresqleu.confreg.models import Conference, ConferenceSession
+from postgresqleu.confreg.models import ConferenceRegistration
+
+from postgresqleu.util.messaging.twitter import Twitter
+
+class Command(BaseCommand):
+       help = 'Send confreg frequent reminders'
+
+       def handle(self, *args, **options):
+               if not settings.TWITTER_CLIENT or not settings.TWITTER_CLIENTSECRET:
+                       return
+
+               curs = connection.cursor()
+               curs.execute("SELECT pg_try_advisory_lock(94012426)")
+               if not curs.fetchall()[0][0]:
+                       raise CommandError("Failed to get advisory lock, existing frequent reminder process stuck?")
+
+               # Only conferences that are actually running right now need to be considered.
+               # Normally this is likely just one.
+               # We can also filter for conferences that actually have reminders active.
+               # Right now that's only twitter reminders, butin the future there cna be
+               # more plugins.
+               for conference in Conference.objects.filter(twitterreminders_active=True,
+                                                                                                       startdate__lte=datetime.today()+timedelta(days=1),
+                                                                                                       enddate__gte=datetime.today()-timedelta(days=1)) \
+                                                                                       .exclude(twitter_token='') \
+                                                                                       .exclude(twitter_secret=''):
+                       tw = Twitter(conference)
+                       with transaction.atomic():
+                               # Sessions that can take reminders (yes we could make a more complete join at one
+                               # step here, but that will likely fall apart later with more integrations anyway)
+                               for s in ConferenceSession.objects.select_related('room') \
+                                                                                                 .filter(conference=conference,
+                                                                                                                 starttime__gt=datetime.now()-timedelta(hours=conference.timediff),
+                                                                                                                 starttime__lt=datetime.now()-timedelta(hours=conference.timediff)+timedelta(minutes=15),
+                                                                                                                 status=1,
+                                                                                                                 reminder_sent=False):
+                                       for reg in ConferenceRegistration.objects.filter(
+                                                       conference=conference,
+                                                       attendee__speaker__conferencesession=s):
+
+                                               msg = """Hello! We'd like to remind you that your session "{0}" is starting soon (at {1}) in room {2}.""".format(
+                                                       s.title,
+                                                       s.starttime.strftime("%H:%M"),
+                                                       s.room.roomname,
+                                               )
+                                               if reg.twittername:
+                                                       # Twitter name registered, so send reminder
+                                                       ok, err = tw.send_message(reg.twittername, msg)
+                                                       if not ok:
+                                                               print("Failed to send twitter DM to {0}: {1}".format(reg.twittername, err))
+
+                                       s.reminder_sent=True
+                                       s.save()
diff --git a/postgresqleu/confreg/migrations/0033_speaker_reminders.py b/postgresqleu/confreg/migrations/0033_speaker_reminders.py
new file mode 100644 (file)
index 0000000..9ac0ba2
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.10 on 2018-10-15 14:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('confreg', '0032_tweetqueue'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='conference',
+            name='twitterreminders_active',
+            field=models.BooleanField(default=False, verbose_name=b'Twitter reminder DMs active'),
+        ),
+        migrations.AddField(
+            model_name='conferencesession',
+            name='reminder_sent',
+            field=models.BooleanField(default=False, verbose_name=b'Speaker reminder(s) sent'),
+        ),
+    ]
index d44923a60c1baa86605e7f1ec17ae4d30a858d3e..4d9c7a9e7e709978c6a727dbaee11cbbe33a5fa0 100644 (file)
@@ -121,6 +121,7 @@ class Conference(models.Model):
        pixelsperminute = models.FloatField(blank=False, default=1.5, null=False, verbose_name="Vertical pixels per minute")
        confurl = models.CharField(max_length=128, blank=False, null=False, validators=[validate_lowercase,], verbose_name="Conference URL")
        twittersync_active = models.BooleanField(null=False, default=False, verbose_name='Twitter posting active')
+       twitterreminders_active = models.BooleanField(null=False, default=False, verbose_name='Twitter reminder DMs active')
        twitter_user = models.CharField(max_length=32, blank=True, null=False)
        twitter_token = models.CharField(max_length=128, blank=True, null=False)
        twitter_secret = models.CharField(max_length=128, blank=True, null=False)
@@ -671,6 +672,7 @@ class ConferenceSession(models.Model):
        tentativescheduleslot = models.ForeignKey(ConferenceSessionScheduleSlot, null=True, blank=True, on_delete=models.CASCADE)
        tentativeroom = models.ForeignKey(Room, null=True, blank=True, related_name='tentativeroom', on_delete=models.CASCADE)
        lastmodified = models.DateTimeField(auto_now=True, null=False, blank=False)
+       reminder_sent = models.BooleanField(null=False, default=False, verbose_name='Speaker reminder(s) sent')
 
        # NOTE! Any added fields need to be considered for inclusion in
        # forms.CallForPapersForm and in views.callforpapers_copy()!