Add function to send email to attendees on waitlist
authorMagnus Hagander <magnus@hagander.net>
Fri, 28 Sep 2018 11:03:49 +0000 (13:03 +0200)
committerMagnus Hagander <magnus@hagander.net>
Fri, 28 Sep 2018 11:03:49 +0000 (13:03 +0200)
docs/confreg/waitlist.md
postgresqleu/confreg/forms.py
postgresqleu/confreg/views.py
postgresqleu/urls.py
template/confreg/admin_waitlist.html
template/confreg/admin_waitlist_sendmail.html [new file with mode: 0644]

index 96189970722db82e95b5e1b23b36ccdb90df3de4..c82e6ad9698015f914326e2e1a8edd7d46626e7a 100644 (file)
@@ -60,3 +60,17 @@ offers can be given.
 ## Waitlist workflow
 
 ![Waitlist workflow](graphs/waitlist.svg)
+
+## Waitlist emails <a name="emails"></a>
+
+It is also possible to send email to all attendees on the
+waitlist. This can be filtered to be all on waitlist, or just the ones
+with or without offers.
+
+Unlike [attendee emails](emails) these are sent directly but are not
+archived on the registration pages or similar, as these are to
+accounts that have not yet got a registration.
+
+If wanted, the position on the waitlist and/or the size of the
+waitlist can be included. If this is selected, a sentence about it
+will be added as a footer of the email.
index 47809cdc6853b5a2e07e36ecb83e8ce7acc2afd9..8b91c6a5ae4ca952dbc1ff0af99de662dffc8a79 100644 (file)
@@ -646,6 +646,46 @@ class WaitlistOfferForm(forms.Form):
                        raise ValidationError("At least one registration must be selected to make an offer")
                return self.cleaned_data
 
+class WaitlistSendmailForm(forms.Form):
+       TARGET_ALL=0
+       TARGET_OFFERS=1
+       TARGET_NOOFFERS=2
+
+       TARGET_CHOICES = (
+               (TARGET_ALL, 'All attendees on waitlist'),
+               (TARGET_OFFERS, 'Only attendees with active offers'),
+               (TARGET_NOOFFERS, 'Only attendees without active offers'),
+       )
+
+       POSITION_NONE=0
+       POSITION_FULL=1
+       POSITION_ONLY=2
+       POSITION_SIZE=3
+       POSITION_CHOICES = (
+               (POSITION_NONE, 'No position information'),
+               (POSITION_FULL, 'Both position and size of waitlist'),
+               (POSITION_ONLY, 'Only position on waitlist'),
+               (POSITION_SIZE, 'Only size of waitlist'),
+       )
+
+       waitlist_target = forms.ChoiceField(required=True, choices=TARGET_CHOICES)
+       subject = forms.CharField(max_length=100, required=True)
+       message = forms.CharField(required=True, widget=forms.Textarea)
+       include_position = forms.ChoiceField(required=True, choices=POSITION_CHOICES,
+                                                                                help_text="Include a footer with information about waitpost position and/or size")
+       confirm = forms.BooleanField(help_text="Confirm that you are ready to send this email!", required=False)
+
+       def __init__(self, conference, *args, **kwargs):
+               self.conference = conference
+               super(WaitlistSendmailForm, self).__init__(*args, **kwargs)
+               if not (self.data.get('subject') and self.data.get('message')):
+                       del self.fields['confirm']
+               self.fields['subject'].help_text = u"Will be prefixed by [{0}]".format(conference.conferencename)
+
+       def clean_confirm(self):
+               if not self.cleaned_data['confirm']:
+                       raise ValidationError("Please check this box to confirm that you are really sending this email! There is no going back!")
+
 class TransferRegForm(forms.Form):
        transfer_from = forms.ModelChoiceField(ConferenceRegistration.objects.filter(id=-1))
        transfer_to = forms.ModelChoiceField(ConferenceRegistration.objects.filter(id=-1))
index 1e451e56d1279abd70687f93728227bfbd5e2ed9..fc17899a98033baa33836fd16693b27daa4f1a34 100644 (file)
@@ -29,7 +29,7 @@ from forms import ConferenceFeedbackForm, SpeakerProfileForm
 from forms import CallForPapersForm, CallForPapersSpeakerForm
 from forms import CallForPapersCopyForm, PrepaidCreateForm, BulkRegistrationForm
 from forms import EmailSendForm, EmailSessionForm, CrossConferenceMailForm
-from forms import AttendeeMailForm, WaitlistOfferForm, TransferRegForm
+from forms import AttendeeMailForm, WaitlistOfferForm, WaitlistSendmailForm, TransferRegForm
 from forms import NewMultiRegForm, MultiRegInvoiceForm
 from forms import SessionSlidesUrlForm, SessionSlidesFileForm
 from util import invoicerows_for_registration, notify_reg_confirmed, InvoicerowsException
@@ -2811,6 +2811,59 @@ def admin_waitlist_cancel(request, urlname, wlid):
        return HttpResponseRedirect("../../")
 
 
+def admin_waitlist_sendmail(request, urlname):
+       conference = get_authenticated_conference(request, urlname)
+
+       if request.method == 'POST':
+               form = WaitlistSendmailForm(conference, data=request.POST)
+               if form.is_valid():
+                       with transaction.atomic():
+                               q = RegistrationWaitlistEntry.objects.filter(registration__conference=conference,
+                                                                                                                        registration__payconfirmedat__isnull=True)
+                               tot = q.all().count()
+                               if not tot:
+                                       messages.warning(request, "Waitlist was empty, no email was sent.")
+                                       return HttpResponseRedirect('../')
+
+                               if int(form.cleaned_data['waitlist_target']) == form.TARGET_OFFERS:
+                                       q = q.filter(offeredon__isnull=False)
+                               elif int(form.cleaned_data['waitlist_target']) == form.TARGET_NOOFFERS:
+                                       q = q.filter(offeredon__isnull=True)
+
+                               n = 0
+                               for w in q.order_by('enteredon'):
+                                       n += 1
+
+                                       msgbody = form.cleaned_data['message']
+                                       if int(form.cleaned_data['include_position']) == form.POSITION_FULL:
+                                               msgbody += "\n\nYour position on the waitlist is {0} of {1}.\n".format(n, tot)
+                                       elif int(form.cleaned_data['include_position']) == form.POSITION_ONLY:
+                                               msgbody += "\n\nYour position on the waitlist is {0}.\n".format(n)
+                                       elif int(form.cleaned_data['include_position']) == form.POSITION_SIZE:
+                                               msgbody += "\n\nThe current size of the waitlist is {0}.\n".format(tot)
+
+                                       send_simple_mail(conference.contactaddr,
+                                                                        conference.contactaddr,
+                                                                        u"[{0}] {1}".format(conference.conferencename, form.cleaned_data['subject']),
+                                                                        msgbody,
+                                                                        sendername=conference.conferencename,
+                                                                        receivername=w.registration.fullname)
+                               if n:
+                                       messages.info(request, "Sent {0} emails.".format(tot))
+                               else:
+                                       messages.warning(request, "No matching waitlist entries, no email was sent.")
+                               return HttpResponseRedirect('../')
+       else:
+               form = WaitlistSendmailForm(conference)
+
+       return render(request, 'confreg/admin_waitlist_sendmail.html', {
+               'conference': conference,
+               'form': form,
+               'helplink': 'waitlist#emails',
+               'breadcrumbs': (('/events/admin/{0}/waitlist/'.format(conference.urlname), 'Waitlist'),),
+       })
+
+
 @transaction.atomic
 def admin_attendeemail(request, urlname):
        conference = get_authenticated_conference(request, urlname)
index 84ab3856d07c32258e2b96746e525dbe3832312e..585348d17e499237535628d62a4f035b10351d64 100644 (file)
@@ -144,6 +144,7 @@ urlpatterns = [
        url(r'^events/admin/([^/]+)/sessionnotifyqueue/$', postgresqleu.confreg.views.session_notify_queue),
        url(r'^events/admin/(\w+)/waitlist/$', postgresqleu.confreg.views.admin_waitlist),
        url(r'^events/admin/(\w+)/waitlist/cancel/(\d+)/$', postgresqleu.confreg.views.admin_waitlist_cancel),
+       url(r'^events/admin/(\w+)/waitlist/sendmail/$', postgresqleu.confreg.views.admin_waitlist_sendmail),
        url(r'^events/admin/(\w+)/wiki/$', postgresqleu.confwiki.views.admin),
        url(r'^events/admin/(\w+)/wiki/(new|\d+)/$', postgresqleu.confwiki.views.admin_edit_page),
        url(r'^events/admin/(\w+)/signups/$', postgresqleu.confwiki.views.signup_admin),
index 2fdd0877b06b77dcb4f84ec10fdc93f5d43d6db5..cf31f62435825027cb5843bc423b4db4505eeea9 100644 (file)
@@ -136,6 +136,11 @@ The current entries on the waitlist are:
 <p>The waitlist is currently empty</p>
 {%endif%}
 
+{%if waitlist%}
+<h3>Operations</h3>
+<a class="btn btn-default" href="sendmail/">Send waitlist email</a>
+{%endif%}
+
 <h3>Processed waitlist</h3>
 {%with waitlist=waitlist_cleared%}
 {%include "confreg/admin_waitlist_list.inc.html"%}
diff --git a/template/confreg/admin_waitlist_sendmail.html b/template/confreg/admin_waitlist_sendmail.html
new file mode 100644 (file)
index 0000000..fcca358
--- /dev/null
@@ -0,0 +1,8 @@
+{%extends "confreg/confadmin_base.html"%}
+{%block title%}Send waitlist email{%endblock%}
+{%block layoutblock%}
+<h1>Send waitlist email</h1>
+<form class="form-horizontal" method="POST" action="." enctype="multipart/form-data">{%csrf_token%}
+{%include "confreg/admin_backend_form_content.html" %}
+</form>
+{%endblock%}