diff --git a/bda/admin.py b/bda/admin.py index 58228e7e..26d9a865 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -20,10 +20,25 @@ class ChoixSpectacleInline(admin.TabularInline): class AttributionInline(admin.TabularInline): model = Attribution + extra = 0 + + def get_queryset(self, request): + qs = super(AttributionInline, self).get_queryset(request) + return qs.filter(spectacle__listing=False) + + +class AttributionInlineListing(admin.TabularInline): + model = Attribution + exclude = ('given', ) + extra = 0 + + def get_queryset(self, request): + qs = super(AttributionInlineListing, self).get_queryset(request) + return qs.filter(spectacle__listing=True) class ParticipantAdmin(admin.ModelAdmin): - inlines = [AttributionInline] + inlines = [AttributionInline, AttributionInlineListing] def get_queryset(self, request): return Participant.objects.annotate(nb_places=Count('attributions'), @@ -164,9 +179,11 @@ class ChoixSpectacleAdmin(admin.ModelAdmin): class SpectacleAdmin(admin.ModelAdmin): model = Spectacle - list_display = ("title", "date", "tirage", "location", "slots", "price") + list_display = ("title", "date", "tirage", "location", "slots", "price", + "listing") list_filter = ("location", "tirage",) search_fields = ("title", "location__name") + readonly_fields = ("rappel_sent", ) class TirageAdmin(admin.ModelAdmin): diff --git a/bda/migrations/0004_mails-rappel.py b/bda/migrations/0004_mails-rappel.py new file mode 100644 index 00000000..f17b711f --- /dev/null +++ b/bda/migrations/0004_mails-rappel.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bda', '0003_update_tirage_and_spectacle'), + ] + + operations = [ + migrations.AddField( + model_name='spectacle', + name='listing', + field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'), + preserve_default=False, + ), + migrations.AddField( + model_name='spectacle', + name='rappel_sent', + field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True), + ), + ] diff --git a/bda/models.py b/bda/models.py index fbb6e0ee..349a71e4 100644 --- a/bda/models.py +++ b/bda/models.py @@ -4,7 +4,16 @@ import calendar from django.db import models from django.contrib.auth.models import User -from django.utils.translation import ugettext_lazy as _ +from django.template import loader, Context +from django.core import mail +from django.conf import settings +from django.utils import timezone + + +def render_template(template_name, data): + tmpl = loader.get_template(template_name) + ctxt = Context(data) + return tmpl.render(ctxt) class Tirage(models.Model): @@ -39,6 +48,9 @@ class Spectacle(models.Model): slots = models.IntegerField("Places") priority = models.IntegerField("Priorité", default=1000) tirage = models.ForeignKey(Tirage) + listing = models.BooleanField("Les places sont sur listing") + rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, + null=True) class Meta: verbose_name = "Spectacle" @@ -57,6 +69,38 @@ class Spectacle(models.Model): return u"%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(), self.location, self.price) + def send_rappel(self): + # On récupère la liste des participants + members = {} + for attr in Attribution.objects.filter(spectacle=self).all(): + member = attr.participant.user + if member.id in members: + members[member.id].nb_attr = 2 + else: + member.nb_attr = 1 + members[member.id] = member + # On écrit un mail personnalisé à chaque participant + mails_to_send = [] + mail_object = "%s - %s - %s" % (self.title, self.date_no_seconds(), + self.location) + for member in members.values(): + mail_body = render_template('mail-rappel.txt', { + 'member': member, + 'show': self}) + mail_tot = mail.EmailMessage( + mail_object, mail_body, + settings.RAPPEL_FROM, [member.email], + [], headers={'Reply-To': settings.RAPPEL_REPLY_TO}) + mails_to_send.append(mail_tot) + # On envoie les mails + connection = mail.get_connection() + connection.send_messages(mails_to_send) + # On enregistre le fait que l'envoi a bien eu lieu + self.rappel_sent = timezone.now() + self.save() + # On renvoie la liste des destinataires + return members.values() + PAYMENT_TYPES = ( ("cash", u"Cash"), ("cb", "CB"), diff --git a/bda/templates/mail-rappel.txt b/bda/templates/mail-rappel.txt new file mode 100644 index 00000000..5152b1db --- /dev/null +++ b/bda/templates/mail-rappel.txt @@ -0,0 +1,23 @@ +Bonjour {{ member.get_full_name }}, + +Nous te rappellons que tu as eu la chance d'obtenir {{ member.nb_attr|pluralize:"une place,deux places" }} +pour {{ show.title }}, le {{ show.date_no_seconds }} au {{ show.location }}. N'oublie pas de t'y rendre ! +{% if member.nb_attr == 2 %} +Tu as obtenu deux places pour ce spectacle. Nous te rappelons que +ces places sont strictement réservées aux personnes de moins de 28 ans. +{% endif %} +{% if show.listing %}Pour ce spectacle, tu as reçu des places sur +listing. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la représentation +pour retirer {{ member.nb_attr|pluralize:"ta place,tes places" }}. +{% else %}Pour assister à ce spectacle, tu dois présenter les billets qui ont +été distribués au burô. +{% endif %} + +Si tu ne peux plus assister à cette représentation, tu peux +revendre ta place via BdA-revente, accessible directement sur +GestioCOF (lien "revendre une place du premier tirage" sur la page +d'accueil https://www.cof.ens.fr/gestion/). + +En te souhaitant un excellent spectacle, + +Le Bureau des Arts diff --git a/bda/templates/mails-rappel.html b/bda/templates/mails-rappel.html new file mode 100644 index 00000000..3fc15fa2 --- /dev/null +++ b/bda/templates/mails-rappel.html @@ -0,0 +1,35 @@ +{% extends "base_title.html" %} + +{% block realcontent %} +{% if sent %} +

Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes

+ +{% else %} +

Voulez vous envoyer les mails de rappel pour le spectacle + {{ show.title }} ?

+ {% if show.rappel_sent %} +

Attention, les mails ont déjà été envoyés le + {{ show.rappel_sent }}

+ {% endif %} +{% endif %} + +{% if not sent %} +
+ {% csrf_token %} +
+ +
+{% endif %} + +

Forme des mails

+ +
Une seule place

+
{{ exemple_mail_1place }}
+ +
Deux places

+
{{ exemple_mail_2places }}
+{% endblock %} diff --git a/bda/urls.py b/bda/urls.py index 4557ec86..47a946ba 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -33,4 +33,5 @@ urlpatterns = patterns( url(r'spectacles/unpaid/(?P\d+)$', "bda.views.unpaid", name="bda-unpaid"), + url(r'mails-rappel/(?P\d+)$', "bda.views.send_rappel"), ) diff --git a/bda/views.py b/bda/views.py index d67044cf..844ef60c 100644 --- a/bda/views.py +++ b/bda/views.py @@ -19,7 +19,7 @@ import time from gestioncof.decorators import cof_required, buro_required from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ - Tirage + Tirage, render_template from bda.algorithm import Algorithm from bda.forms import BaseBdaFormSet, TokenForm, ResellForm @@ -249,7 +249,7 @@ def do_tirage(request, tirage_id): # À partir d'ici, le tirage devient effectif # FIXME: Établir les conditions de validations (formulaire ?) # cf. issue #32 - if False: + if True: Attribution.objects.filter( spectacle__tirage=tirage_elt ).delete() @@ -370,3 +370,31 @@ def liste_spectacles_ics(request, tirage_id): return render(request, "liste_spectacles.ics", {"spectacles": spectacles, "tirage": tirage}, content_type="text/calendar") + + +@buro_required +def send_rappel(request, spectacle_id): + show = get_object_or_404(Spectacle, id=spectacle_id) + # Mails d'exemples + fake_member = request.user + fake_member.nb_attr = 1 + exemple_mail_1place = render_template('mail-rappel.txt', { + 'member': fake_member, + 'show': show}) + fake_member.nb_attr = 2 + exemple_mail_2places = render_template('mail-rappel.txt', { + 'member': fake_member, + 'show': show}) + # Contexte + ctxt = {'show': show, + 'exemple_mail_1place': exemple_mail_1place, + 'exemple_mail_2places': exemple_mail_2places} + # Envoi confirmé + if request.method == 'POST': + members = show.send_rappel() + ctxt['sent'] = True + ctxt['members'] = members + # Demande de confirmation + else: + ctxt['sent'] = False + return render(request, "mails-rappel.html", ctxt) diff --git a/cof/settings_dev.py b/cof/settings_dev.py index ee1c6c7e..0cd05787 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -137,6 +137,9 @@ PETITS_COURS_FROM = "Le COF " PETITS_COURS_BCC = "archivescof@gmail.com" PETITS_COURS_REPLYTO = "cof@ens.fr" +RAPPEL_FROM = 'Le BdA ' +RAPPEL_REPLY_TO = RAPPEL_FROM + LOGIN_URL = "/login" LOGIN_REDIRECT_URL = "/" diff --git a/gestioncof/migrations/0004_registration_mail.py b/gestioncof/migrations/0004_registration_mail.py new file mode 100644 index 00000000..97caff31 --- /dev/null +++ b/gestioncof/migrations/0004_registration_mail.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + +def create_mail(apps, schema_editor): + CustomMail = apps.get_model("gestioncof", "CustomMail") + db_alias = schema_editor.connection.alias + if CustomMail.objects.filter(shortname="bienvenue").count() == 0: + CustomMail.objects.using(db_alias).bulk_create([ + CustomMail( + shortname="bienvenue", + title="Bienvenue au COF", + content="Mail de bienvenue au COF, envoyé automatiquement à " \ + + "l'inscription.\n\n" \ + + "Les balises {{ ... }} sont interprétées comme expliqué " \ + + "ci-dessous à l'envoi.", + comments="{{ nom }} \t fullname de la personne.\n"\ + + "{{ prenom }} \t prénom de la personne.") + ]) + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0002_enable_unprocessed_demandes'), + ] + + operations = [ + # Pas besoin de supprimer le mail lors de la migration dans l'autre + # sens. + migrations.RunPython(create_mail, migrations.RunPython.noop), + ]