Merge branch 'Elarnon/mail_bda' into 'master'

Améliore les mails automatiques du BdA

Les mails du BdA sont maintenant tous chargés depuis des templates gérés par le système de templates de Django, et plus par de l'interpolation de chaîne de caractères. Ceci permet en particulier d'utiliser (et de configurer) la localisation de Django afin d'afficher les dates de façon uniforme (et sans "hack" à la `date_no_seconds`) dans un format comportant un "à" entre le jour et l'heure.

See merge request !113
This commit is contained in:
Martin Pepin 2016-11-20 16:53:29 +01:00
commit d6dd7b346c
21 changed files with 133 additions and 104 deletions

View file

@ -1,16 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Gestion en ligne de commande des reventes.
"""
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import timedelta
from django.core.management import BaseCommand from django.core.management import BaseCommand
from django.utils import timezone from django.utils import timezone
from datetime import timedelta
from bda.models import SpectacleRevente from bda.models import SpectacleRevente
class Command(BaseCommand): class Command(BaseCommand):
help = "Envoie les mails de notification et effectue " \ help = "Envoie les mails de notification et effectue " \
"les tirages au sort des reventes" "les tirages au sort des reventes"
leave_locale_alone = True
def handle(self, *args, **options): def handle(self, *args, **options):
now = timezone.now() now = timezone.now()

View file

@ -1,16 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Gestion en ligne de commande des mails de rappel.
"""
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import timedelta
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone from django.utils import timezone
from datetime import timedelta
from bda.models import Spectacle from bda.models import Spectacle
class Command(BaseCommand): class Command(BaseCommand):
help = 'Envoie les mails de rappel des spectacles dont la date ' \ help = 'Envoie les mails de rappel des spectacles dont la date ' \
'approche.\nNe renvoie pas les mails déjà envoyés.' 'approche.\nNe renvoie pas les mails déjà envoyés.'
leave_locale_alone = True
def handle(self, *args, **options): def handle(self, *args, **options):
now = timezone.now() now = timezone.now()

View file

@ -11,19 +11,13 @@ from datetime import timedelta
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
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.template import loader, Context from django.template import loader
from django.core import mail from django.core import mail
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone, formats
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
def render_template(template_name, data):
tmpl = loader.get_template(template_name)
ctxt = Context(data)
return tmpl.render(ctxt)
@python_2_unicode_compatible @python_2_unicode_compatible
class Tirage(models.Model): class Tirage(models.Model):
title = models.CharField("Titre", max_length=300) title = models.CharField("Titre", max_length=300)
@ -34,12 +28,9 @@ class Tirage(models.Model):
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
default=False) default=False)
def date_no_seconds(self):
return self.fermeture.astimezone(timezone.get_current_timezone()) \
.strftime('%d %b %Y %H:%M')
def __str__(self): def __str__(self):
return "%s - %s" % (self.title, self.date_no_seconds()) return "%s - %s" % (self.title, formats.localize(
timezone.template_localtime(self.fermeture)))
@python_2_unicode_compatible @python_2_unicode_compatible
@ -89,15 +80,19 @@ class Spectacle(models.Model):
def timestamp(self): def timestamp(self):
return "%d" % calendar.timegm(self.date.utctimetuple()) return "%d" % calendar.timegm(self.date.utctimetuple())
def date_no_seconds(self):
return self.date.astimezone(timezone.get_current_timezone()) \
.strftime('%d %b %Y %H:%M')
def __str__(self): def __str__(self):
return "%s - %s, %s, %.02f" % (self.title, self.date_no_seconds(), return "%s - %s, %s, %.02f" % (
self.location, self.price) self.title,
formats.localize(timezone.template_localtime(self.date)),
self.location,
self.price
)
def send_rappel(self): def send_rappel(self):
"""
Envoie un mail de rappel à toutes les personnes qui ont une place pour
ce spectacle.
"""
# On récupère la liste des participants # On récupère la liste des participants
members = {} members = {}
for attr in Attribution.objects.filter(spectacle=self).all(): for attr in Attribution.objects.filter(spectacle=self).all():
@ -111,10 +106,9 @@ class Spectacle(models.Model):
members[-1] = ['BdA', 2, 'bda@ens.fr'] members[-1] = ['BdA', 2, 'bda@ens.fr']
# On écrit un mail personnalisé à chaque participant # On écrit un mail personnalisé à chaque participant
mails_to_send = [] mails_to_send = []
mail_object = "%s - %s - %s" % (self.title, self.date_no_seconds(), mail_object = str(self)
self.location)
for member in members.values(): for member in members.values():
mail_body = render_template('mail-rappel.txt', { mail_body = loader.render_to_string('bda/mails/rappel.txt', {
'name': member[0], 'name': member[0],
'nb_attr': member[1], 'nb_attr': member[1],
'show': self}) 'show': self})
@ -274,7 +268,7 @@ class SpectacleRevente(models.Model):
mails_to_send = [] mails_to_send = []
mail_object = "%s" % (self.attribution.spectacle) mail_object = "%s" % (self.attribution.spectacle)
for participant in inscrits: for participant in inscrits:
mail_body = render_template('mail-revente.txt', { mail_body = loader.render_to_string('bda/mails/revente.txt', {
'user': participant.user, 'user': participant.user,
'spectacle': self.attribution.spectacle, 'spectacle': self.attribution.spectacle,
'revente': self, 'revente': self,
@ -293,12 +287,16 @@ class SpectacleRevente(models.Model):
self.save() self.save()
def mail_shotgun(self): def mail_shotgun(self):
"""
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
leur indiquer qu'il est désormais disponible au shotgun.
"""
inscrits = self.attribution.spectacle.subscribed.select_related('user') inscrits = self.attribution.spectacle.subscribed.select_related('user')
mails_to_send = [] mails_to_send = []
mail_object = "%s" % (self.attribution.spectacle) mail_object = "%s" % (self.attribution.spectacle)
for participant in inscrits: for participant in inscrits:
mail_body = render_template('mail-shotgun.txt', { mail_body = loader.render_to_string('bda/mails/shotgun.txt', {
'user': participant.user, 'user': participant.user,
'spectacle': self.attribution.spectacle, 'spectacle': self.attribution.spectacle,
'domain': Site.objects.get_current(), 'domain': Site.objects.get_current(),
@ -317,6 +315,11 @@ class SpectacleRevente(models.Model):
self.save() self.save()
def tirage(self): def tirage(self):
"""
Lance le tirage au sort associé à la revente. Un gagnant est choisi
parmis les personnes intéressées par le spectacle. Les personnes sont
ensuites prévenues par mail du résultat du tirage.
"""
inscrits = list(self.answered_mail.all()) inscrits = list(self.answered_mail.all())
spectacle = self.attribution.spectacle spectacle = self.attribution.spectacle
seller = self.seller seller = self.seller
@ -334,13 +337,17 @@ class SpectacleRevente(models.Model):
'spectacle': spectacle, 'spectacle': spectacle,
} }
mails.append(mail.EmailMessage( mails.append(mail.EmailMessage(
mail_subject, loader.render_to_string('mail-revente-winner.txt', context), mail_subject,
loader.render_to_string('bda/mails/revente-winner.txt',
context),
from_email=settings.MAIL_DATA['revente']['FROM'], from_email=settings.MAIL_DATA['revente']['FROM'],
to=[winner.user.email], to=[winner.user.email],
reply_to=[seller.user.email], reply_to=[seller.user.email],
)) ))
mails.append(mail.EmailMessage( mails.append(mail.EmailMessage(
mail_subject, loader.render_to_string('mail-revente-seller.txt', context), mail_subject,
loader.render_to_string('bda/mails/revente-seller.txt',
context),
from_email=settings.MAIL_DATA['revente']['FROM'], from_email=settings.MAIL_DATA['revente']['FROM'],
to=[seller.user.email], to=[seller.user.email],
reply_to=[winner.user.email], reply_to=[winner.user.email],
@ -351,11 +358,12 @@ class SpectacleRevente(models.Model):
if inscrit == winner: if inscrit == winner:
continue continue
mail_body = loader.render_to_string('mail-revente-loser.txt', { mail_body = loader.render_to_string(
'acheteur': inscrit.user, 'bda/mails/revente-loser.txt',
{'acheteur': inscrit.user,
'vendeur': seller.user, 'vendeur': seller.user,
'spectacle': spectacle, 'spectacle': spectacle}
}) )
mails.append(mail.EmailMessage( mails.append(mail.EmailMessage(
mail_subject, mail_body, mail_subject, mail_body,
from_email=settings.MAIL_DATA['revente']['FROM'], from_email=settings.MAIL_DATA['revente']['FROM'],

View file

@ -22,7 +22,7 @@
{% for show, members, losers in results %} {% for show, members, losers in results %}
<div class="attribresult"> <div class="attribresult">
<h3 class="horizontal-title">{{ show.title }} - {{ show.date_no_seconds }} @ {{ show.location }}</h3> <h3 class="horizontal-title">{{ show.title }} - {{ show.date }} @ {{ show.location }}</h3>
<p> <p>
<strong>{{ show.nrequests }} demandes pour {{ show.slots }} places</strong> <strong>{{ show.nrequests }} demandes pour {{ show.slots }} places</strong>
{{ show.price }}€ par place{% if user.profile.is_buro and show.nrequests < show.slots %}, {{ show.deficit }} de déficit{% endif %} {{ show.price }}€ par place{% if user.profile.is_buro and show.nrequests < show.slots %}, {{ show.deficit }} de déficit{% endif %}

View file

@ -0,0 +1,6 @@
Bonjour {{ vendeur.first_name }} !
Je souhaiterais racheter ta place pour {{ spectacle.title }} le {{ spectacle.date }} ({{ spectacle.location }}) à {{ spectacle.price|floatformat:2 }}€.
Contacte-moi si tu es toujours intéressé·e !
{{ acheteur.get_full_name }} ({{ acheteur.email }})

View file

@ -1,7 +1,7 @@
Bonjour {{ name }}, Bonjour {{ name }},
Nous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:"une place,deux places" }} Nous te rappellons que tu as eu la chance d'obtenir {{ 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 ! pour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !
{% if nb_attr == 2 %} {% if nb_attr == 2 %}
Tu as obtenu deux places pour ce spectacle. Nous te rappelons que 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. ces places sont strictement réservées aux personnes de moins de 28 ans.

View file

@ -1,6 +1,6 @@
Bonjour {{ acheteur.first_name }}, Bonjour {{ acheteur.first_name }},
Tu as été tiré-e au sort pour racheter une place pour {{ spectacle.title }} le {{ spectacle.date_no_seconds }} ({{ spectacle.location }}) à {{ spectacle.price|floatformat:2 }}€. Tu as été tiré-e au sort pour racheter une place pour {{ spectacle.title }} le {{ spectacle.date }} ({{ spectacle.location }}) à {{ spectacle.price|floatformat:2 }}€.
Tu peux contacter le/la vendeur-se à l'adresse {{ vendeur.email }}, ou en répondant à ce mail. Tu peux contacter le/la vendeur-se à l'adresse {{ vendeur.email }}, ou en répondant à ce mail.
Chaleureusement, Chaleureusement,

View file

@ -1,6 +1,6 @@
Bonjour {{ user.first_name }} Bonjour {{ user.first_name }}
Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date_no_seconds }}) Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date }})
a été postée sur BdA-Revente. a été postée sur BdA-Revente.
Si ce spectacle t'intéresse toujours, merci de nous le signaler en cliquant Si ce spectacle t'intéresse toujours, merci de nous le signaler en cliquant

View file

@ -1,10 +1,10 @@
Bonjour {{ user.first_name }} Bonjour {{ user.first_name }}
Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date_no_seconds }}) Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date }})
a été postée sur BdA-Revente. a été postée sur BdA-Revente.
Puisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour Puisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour
cette place : elle est disponible immédiatement à l'addresse cette place : elle est disponible immédiatement à l'adresse
http://{{ domain }}{% url "bda-buy-revente" spectacle.id %}, à la disposition de tous. http://{{ domain }}{% url "bda-buy-revente" spectacle.id %}, à la disposition de tous.
Chaleureusement, Chaleureusement,

View file

@ -18,7 +18,7 @@
{% for spectacle in spectacles %} {% for spectacle in spectacles %}
<tr> <tr>
<td>{{ spectacle.title }}</td> <td>{{ spectacle.title }}</td>
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date_no_seconds }}</td> <td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td> <td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td> <td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
<td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</td> <td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</td>

View file

@ -11,7 +11,7 @@
<tr> <tr>
<td>{{place.spectacle.title}}</td> <td>{{place.spectacle.title}}</td>
<td>{{place.spectacle.location}}</td> <td>{{place.spectacle.location}}</td>
<td>{{place.spectacle.date_no_seconds}}</td> <td>{{place.spectacle.date}}</td>
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td> <td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -19,7 +19,7 @@
{% for spectacle in object_list %} {% for spectacle in object_list %}
<tr class="clickable-row" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}"> <tr class="clickable-row" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
<td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td> <td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td>
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date_no_seconds }}</td> <td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td> <td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}"> <td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
{{ spectacle.price |floatformat }}€ {{ spectacle.price |floatformat }}€

View file

@ -27,7 +27,7 @@ from datetime import timedelta
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, render_template, SpectacleRevente Tirage, SpectacleRevente
from bda.algorithm import Algorithm from bda.algorithm import Algorithm
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
@ -286,7 +286,7 @@ def revente(request, tirage_id):
revente.seller = participant revente.seller = participant
revente.date = timezone.now() revente.date = timezone.now()
mail_subject = "BdA-Revente : {:s}".format(attribution.spectacle.title) mail_subject = "BdA-Revente : {:s}".format(attribution.spectacle.title)
mail_body = loader.render_to_string('mail-revente-new.txt', { mail_body = loader.render_to_string('bda/mails/revente-new.txt', {
'vendeur': participant.user, 'vendeur': participant.user,
'spectacle': attribution.spectacle, 'spectacle': attribution.spectacle,
'revente': revente, 'revente': revente,
@ -453,14 +453,11 @@ def buy_revente(request, spectacle_id):
revente = random.choice(reventes_shotgun) revente = random.choice(reventes_shotgun)
revente.soldTo = participant revente.soldTo = participant
revente.save() revente.save()
mail = """Bonjour ! mail = loader.render_to_string('bda/mails/buy-shotgun.txt', {
'spectacle': spectacle,
Je souhaiterais racheter ta place pour %s le %s (%s) à %.02f. 'acheteur': request.user,
Contacte-moi si tu es toujours intéressé·e ! 'vendeur': revente.seller.user,
})
%s (%s)""" % (spectacle.title, spectacle.date_no_seconds(),
spectacle.location, spectacle.price,
request.user.get_full_name(), request.user.email)
send_mail("BdA-Revente : %s" % spectacle.title, mail, send_mail("BdA-Revente : %s" % spectacle.title, mail,
request.user.email, request.user.email,
[revente.seller.user.email], [revente.seller.user.email],
@ -553,11 +550,11 @@ def send_rappel(request, spectacle_id):
# Mails d'exemples # Mails d'exemples
fake_member = request.user fake_member = request.user
fake_member.nb_attr = 1 fake_member.nb_attr = 1
exemple_mail_1place = render_template('mail-rappel.txt', { exemple_mail_1place = loader.render_to_string('bda/mails/rappel.txt', {
'member': fake_member, 'member': fake_member,
'show': show}) 'show': show})
fake_member.nb_attr = 2 fake_member.nb_attr = 2
exemple_mail_2places = render_template('mail-rappel.txt', { exemple_mail_2places = loader.render_to_string('bda/mails/rappel.txt', {
'member': fake_member, 'member': fake_member,
'show': show}) 'show': show})
# Contexte # Contexte

0
cof/locale/__init__.py Normal file
View file

View file

9
cof/locale/fr/formats.py Normal file
View file

@ -0,0 +1,9 @@
# -*- encoding: utf-8 -*-
"""
Formats français.
"""
from __future__ import unicode_literals
DATETIME_FORMAT = r'j F Y \à H:i'

View file

@ -200,3 +200,5 @@ def show_toolbar(request):
DEBUG_TOOLBAR_CONFIG = { DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': show_toolbar, 'SHOW_TOOLBAR_CALLBACK': show_toolbar,
} }
FORMAT_MODULE_PATH = 'cof.locale'

View file

@ -14,7 +14,7 @@ from django.contrib.auth.models import User
from django.views.generic import ListView from django.views.generic import ListView
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.template import loader, Context from django.template import loader
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Min from django.db.models import Min
@ -33,12 +33,6 @@ import base64
import json import json
def render_template(template_path, data):
tmpl = loader.get_template(template_path)
context = Context(data)
return tmpl.render(context)
class DemandeListView(ListView): class DemandeListView(ListView):
model = PetitCoursDemande model = PetitCoursDemande
template_name = "petits_cours_demandes_list.html" template_name = "petits_cours_demandes_list.html"
@ -137,8 +131,8 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
proposed_for = proposed_for.items() proposed_for = proposed_for.items()
attribdata = list(attribdata.items()) attribdata = list(attribdata.items())
proposed_mails = _generate_eleve_email(demande, proposed_for) proposed_mails = _generate_eleve_email(demande, proposed_for)
mainmail = render_template("petits-cours-mail-demandeur.txt", mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
{"proposals": proposals, "proposals": proposals,
"unsatisfied": unsatisfied, "unsatisfied": unsatisfied,
"extra": "extra":
'<textarea name="extra" ' '<textarea name="extra" '
@ -163,8 +157,10 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
def _generate_eleve_email(demande, proposed_for): def _generate_eleve_email(demande, proposed_for):
proposed_mails = [] proposed_mails = []
for user, matieres in proposed_for: for user, matieres in proposed_for:
msg = render_template("petits-cours-mail-eleve.txt", msg = loader.render_to_string("petits-cours-mail-eleve.txt", {
{"demande": demande, "matieres": matieres}) "demande": demande,
"matieres": matieres
})
proposed_mails.append((user, msg)) proposed_mails.append((user, msg))
return proposed_mails return proposed_mails
@ -278,10 +274,11 @@ def _traitement_post(request, demande):
proposals_list = proposals.items() proposals_list = proposals.items()
proposed_for = proposed_for.items() proposed_for = proposed_for.items()
proposed_mails = _generate_eleve_email(demande, proposed_for) proposed_mails = _generate_eleve_email(demande, proposed_for)
mainmail = render_template("petits-cours-mail-demandeur.txt", mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
{"proposals": proposals_list, "proposals": proposals_list,
"unsatisfied": unsatisfied, "unsatisfied": unsatisfied,
"extra": extra}) "extra": extra,
})
frommail = settings.MAIL_DATA['petits_cours']['FROM'] frommail = settings.MAIL_DATA['petits_cours']['FROM']
bccaddress = settings.MAIL_DATA['petits_cours']['BCC'] bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO'] replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']