forked from DGNum/gestioCOF
Merge branch 'Kerl/mails_rappel' into 'master'
Mails de rappel des spectacles BdA Ce patch ajoute la possibilité d'envoyer les mails de rappel pour les spectacles du BdA en demandant l'url `/bda/mails-rappel/<id>` où `<id>` est l'id d'un spectacle. Une fois les mails envoyés, on affiche la liste des participants et le modèle du mail. Normalement pas de relecture nécessaire, c'est là pour le debug et pour permettre au BdA de vérifier qu'il ne s'est pas passé un truc absurde à l'envoi. De plus, cette vue est vouée à être appelée par un script/tâche cron. Il introduit aussi un attribut `listing` aux spectacles pour préciser si les places sont physiques ou non. Ça permet de faire des mails de rappel plus spécifiques et implique des petits changements dans l'interface admin sur la vue des participants. Après avoir appliqué ce patch, il faut lancer la commande `python manage.py migrate`. Fixes #39 See merge request !29
This commit is contained in:
commit
65d17047ff
8 changed files with 181 additions and 5 deletions
21
bda/admin.py
21
bda/admin.py
|
@ -20,10 +20,25 @@ class ChoixSpectacleInline(admin.TabularInline):
|
||||||
|
|
||||||
class AttributionInline(admin.TabularInline):
|
class AttributionInline(admin.TabularInline):
|
||||||
model = Attribution
|
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):
|
class ParticipantAdmin(admin.ModelAdmin):
|
||||||
inlines = [AttributionInline]
|
inlines = [AttributionInline, AttributionInlineListing]
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
return Participant.objects.annotate(nb_places=Count('attributions'),
|
||||||
|
@ -164,9 +179,11 @@ class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
class SpectacleAdmin(admin.ModelAdmin):
|
class SpectacleAdmin(admin.ModelAdmin):
|
||||||
model = Spectacle
|
model = Spectacle
|
||||||
list_display = ("title", "date", "tirage", "location", "slots", "price")
|
list_display = ("title", "date", "tirage", "location", "slots", "price",
|
||||||
|
"listing")
|
||||||
list_filter = ("location", "tirage",)
|
list_filter = ("location", "tirage",)
|
||||||
search_fields = ("title", "location__name")
|
search_fields = ("title", "location__name")
|
||||||
|
readonly_fields = ("rappel_sent", )
|
||||||
|
|
||||||
|
|
||||||
class TirageAdmin(admin.ModelAdmin):
|
class TirageAdmin(admin.ModelAdmin):
|
||||||
|
|
25
bda/migrations/0004_mails-rappel.py
Normal file
25
bda/migrations/0004_mails-rappel.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,7 +4,16 @@ import calendar
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
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):
|
class Tirage(models.Model):
|
||||||
|
@ -39,6 +48,9 @@ class Spectacle(models.Model):
|
||||||
slots = models.IntegerField("Places")
|
slots = models.IntegerField("Places")
|
||||||
priority = models.IntegerField("Priorité", default=1000)
|
priority = models.IntegerField("Priorité", default=1000)
|
||||||
tirage = models.ForeignKey(Tirage)
|
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:
|
class Meta:
|
||||||
verbose_name = "Spectacle"
|
verbose_name = "Spectacle"
|
||||||
|
@ -57,6 +69,38 @@ class Spectacle(models.Model):
|
||||||
return u"%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(),
|
return u"%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(),
|
||||||
self.location, self.price)
|
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 = (
|
PAYMENT_TYPES = (
|
||||||
("cash", u"Cash"),
|
("cash", u"Cash"),
|
||||||
("cb", "CB"),
|
("cb", "CB"),
|
||||||
|
|
23
bda/templates/mail-rappel.txt
Normal file
23
bda/templates/mail-rappel.txt
Normal file
|
@ -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
|
35
bda/templates/mails-rappel.html
Normal file
35
bda/templates/mails-rappel.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
{% if sent %}
|
||||||
|
<h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3>
|
||||||
|
<ul>
|
||||||
|
{% for member in members %}
|
||||||
|
<li>{{ member.get_full_name }} ({{ member.email }})</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<h3>Voulez vous envoyer les mails de rappel pour le spectacle
|
||||||
|
{{ show.title }} ?</h3>
|
||||||
|
{% if show.rappel_sent %}
|
||||||
|
<p class="error">Attention, les mails ont déjà été envoyés le
|
||||||
|
{{ show.rappel_sent }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not sent %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<br />
|
||||||
|
<input type="submit" value="Envoyer" />
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h3>Forme des mails</h3>
|
||||||
|
|
||||||
|
<br />Une seule place<br /><br />
|
||||||
|
<pre>{{ exemple_mail_1place }}</pre>
|
||||||
|
|
||||||
|
<br />Deux places<br /><br />
|
||||||
|
<pre>{{ exemple_mail_2places }}</pre>
|
||||||
|
{% endblock %}
|
|
@ -33,4 +33,5 @@ urlpatterns = patterns(
|
||||||
url(r'spectacles/unpaid/(?P<tirage_id>\d+)$',
|
url(r'spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||||
"bda.views.unpaid",
|
"bda.views.unpaid",
|
||||||
name="bda-unpaid"),
|
name="bda-unpaid"),
|
||||||
|
url(r'mails-rappel/(?P<spectacle_id>\d+)$', "bda.views.send_rappel"),
|
||||||
)
|
)
|
||||||
|
|
32
bda/views.py
32
bda/views.py
|
@ -19,7 +19,7 @@ import time
|
||||||
|
|
||||||
from gestioncof.decorators import cof_required, buro_required
|
from gestioncof.decorators import cof_required, buro_required
|
||||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
||||||
Tirage
|
Tirage, render_template
|
||||||
from bda.algorithm import Algorithm
|
from bda.algorithm import Algorithm
|
||||||
|
|
||||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm
|
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm
|
||||||
|
@ -249,7 +249,7 @@ def do_tirage(request, tirage_id):
|
||||||
# À partir d'ici, le tirage devient effectif
|
# À partir d'ici, le tirage devient effectif
|
||||||
# FIXME: Établir les conditions de validations (formulaire ?)
|
# FIXME: Établir les conditions de validations (formulaire ?)
|
||||||
# cf. issue #32
|
# cf. issue #32
|
||||||
if False:
|
if True:
|
||||||
Attribution.objects.filter(
|
Attribution.objects.filter(
|
||||||
spectacle__tirage=tirage_elt
|
spectacle__tirage=tirage_elt
|
||||||
).delete()
|
).delete()
|
||||||
|
@ -370,3 +370,31 @@ def liste_spectacles_ics(request, tirage_id):
|
||||||
return render(request, "liste_spectacles.ics",
|
return render(request, "liste_spectacles.ics",
|
||||||
{"spectacles": spectacles, "tirage": tirage},
|
{"spectacles": spectacles, "tirage": tirage},
|
||||||
content_type="text/calendar")
|
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)
|
||||||
|
|
|
@ -136,6 +136,9 @@ PETITS_COURS_FROM = "Le COF <cof@ens.fr>"
|
||||||
PETITS_COURS_BCC = "archivescof@gmail.com"
|
PETITS_COURS_BCC = "archivescof@gmail.com"
|
||||||
PETITS_COURS_REPLYTO = "cof@ens.fr"
|
PETITS_COURS_REPLYTO = "cof@ens.fr"
|
||||||
|
|
||||||
|
RAPPEL_FROM = 'Le BdA <bda@ens.fr>'
|
||||||
|
RAPPEL_REPLY_TO = RAPPEL_FROM
|
||||||
|
|
||||||
LOGIN_URL = "/login"
|
LOGIN_URL = "/login"
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue