Merge branch 'Kerl/mail2bda' into 'master'

Copie des mails de rappel pour le BdA

* Le BdA est en copie des mails de rappel (création d'un utilisateur générique BdA à cette fin)
* Un lien vers la page d'envoi manuel des mails de rappel est ajouté à la page qui récapitule les situations des participants au spectacle
* Quelques modification mineures de cette page
* Un test très simple vérifie que les pages mentionnées plus haut sont accessibles à un utilisateur COF authentifié

See merge request !238
This commit is contained in:
Aurélien Delobelle 2017-06-21 05:43:42 +02:00
commit 398893b904
8 changed files with 131 additions and 67 deletions

View file

@ -0,0 +1 @@

View file

@ -7,11 +7,20 @@ from custommail.shortcuts import send_mass_custom_mail
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.db.models import Count
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.utils import timezone, formats from django.utils import timezone, formats
def get_generic_user():
generic, _ = User.objects.get_or_create(
username="bda_generic",
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"}
)
return generic
class Tirage(models.Model): class Tirage(models.Model):
title = models.CharField("Titre", max_length=300) title = models.CharField("Titre", max_length=300)
ouverture = models.DateTimeField("Date et heure d'ouverture du tirage") ouverture = models.DateTimeField("Date et heure d'ouverture du tirage")
@ -96,32 +105,29 @@ class Spectacle(models.Model):
Envoie un mail de rappel à toutes les personnes qui ont une place pour Envoie un mail de rappel à toutes les personnes qui ont une place pour
ce spectacle. ce spectacle.
""" """
# On récupère la liste des participants # On récupère la liste des participants + le BdA
members = {} members = list(
for attr in Attribution.objects.filter(spectacle=self).all(): User.objects
member = attr.participant.user .filter(participant__attributions=self)
if member.id in members: .annotate(nb_attr=Count("id")).order_by()
members[member.id][1] = 2 )
else: bda_generic = get_generic_user()
members[member.id] = [member, 1] bda_generic.nb_attr = 1
# FIXME : faire quelque chose de ça, un utilisateur bda_generic ? members.append(bda_generic)
# # Pour le BdA
# members[0] = ['BdA', 1, 'bda@ens.fr']
# members[-1] = ['BdA', 2, 'bda@ens.fr']
# On écrit un mail personnalisé à chaque participant # On écrit un mail personnalisé à chaque participant
datatuple = [( datatuple = [(
'bda-rappel', 'bda-rappel',
{'member': member[0], 'nb_attr': member[1], 'show': self}, {'member': member, "nb_attr": member.nb_attr, 'show': self},
settings.MAIL_DATA['rappels']['FROM'], settings.MAIL_DATA['rappels']['FROM'],
[member[0].email]) [member.email])
for member in members.values() for member in members
] ]
send_mass_custom_mail(datatuple) send_mass_custom_mail(datatuple)
# On enregistre le fait que l'envoi a bien eu lieu # On enregistre le fait que l'envoi a bien eu lieu
self.rappel_sent = timezone.now() self.rappel_sent = timezone.now()
self.save() self.save()
# On renvoie la liste des destinataires # On renvoie la liste des destinataires
return members.values() return members
@property @property
def is_past(self): def is_past(self):

View file

@ -3,41 +3,46 @@
{% block realcontent %} {% block realcontent %}
<h2>Mails de rappels</h2> <h2>Mails de rappels</h2>
{% if sent %} {% if sent %}
<h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3> <h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3>
<ul> <ul>
{% for member in members %} {% for member in members %}
<li>{{ member.get_full_name }} ({{ member.email }})</li> <li>{{ member.get_full_name }} ({{ member.email }})</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% else %} {% else %}
<h3>Voulez vous envoyer les mails de rappel pour le spectacle <h3>Voulez vous envoyer les mails de rappel pour le spectacle {{ show.title }}&nbsp;?</h3>
{{ show.title }}&nbsp;?</h3>
{% if show.rappel_sent %}
<p class="error">Attention, les mails ont déjà été envoyés le
{{ show.rappel_sent }}</p>
{% endif %}
{% endif %} {% endif %}
{% if not sent %} <div class="empty-form">
<form action="" method="post"> {% if not sent %}
{% csrf_token %} <form action="" method="post">
<div class="pull-right"> {% csrf_token %}
<input class="btn btn-primary" type="submit" value="Envoyer" /> <div class="pull-right">
</div> <input class="btn btn-primary" type="submit" value="Envoyer" />
</form> </div>
{% endif %} </form>
{% endif %}
</div>
<hr \>
<p>
<em>Note :</em> le template de ce mail peut être modifié à
<a href="{% url 'admin:custommail_custommail_change' custommail.pk %}">cette adresse</a>
</p>
<hr \>
<br/>
<hr/>
<h3>Forme des mails</h3> <h3>Forme des mails</h3>
<h4>Une seule place</h4> <h4>Une seule place</h4>
{% for part in exemple_mail_1place %} {% for part in exemple_mail_1place %}
<pre>{{ part }}</pre> <pre>{{ part }}</pre>
{% endfor %} {% endfor %}
<h4>Deux places</h4> <h4>Deux places</h4>
{% for part in exemple_mail_2places %} {% for part in exemple_mail_2places %}
<pre>{{ part }}</pre> <pre>{{ part }}</pre>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View file

@ -36,17 +36,26 @@
</tbody> </tbody>
</table> </table>
<h3><a href="{% url "admin:bda_attribution_add" %}?spectacle={{spectacle.id}}"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter une attribution</a></h3> <h3><a href="{% url "admin:bda_attribution_add" %}?spectacle={{spectacle.id}}"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter une attribution</a></h3>
<br> <div>
<button class="btn btn-default" type="button" onclick="toggle('export-mails')">Afficher/Cacher mails participants</button> <div>
<pre id="export-mails" style="display:none"> <button class="btn btn-default" type="button" onclick="toggle('export-mails')">Afficher/Cacher mails participants</button>
{%for participant in participants %}{{participant.email}}, {%endfor%} <pre id="export-mails" style="display:none">{% spaceless %}
</pre> {% for participant in participants %}{{ participant.email }}, {% endfor %}
<br> {% endspaceless %}</pre>
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button> </div>
<pre id="export-salle" style="display:none">
<div>
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
<pre id="export-salle" style="display:none">{% spaceless %}
{% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places {% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places
{% endfor %} {% endfor %}
</pre> {% endspaceless %}</pre>
</div>
<div>
<a href="{% url 'bda-rappels' spectacle.id %}">Page d'envoi manuel des mails de rappel</a>
</div>
<script type="text/javascript" <script type="text/javascript"
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script> src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
<script> <script>

View file

@ -1,5 +1,6 @@
import json import json
from django.contrib.auth.models import User
from django.test import TestCase, Client from django.test import TestCase, Client
from django.utils import timezone from django.utils import timezone
@ -34,11 +35,36 @@ class TestBdAViews(TestCase):
), ),
]) ])
self.bda_user = User.objects.create_user(
username="bda_user", password="bda4ever"
)
self.bda_user.profile.is_cof = True
self.bda_user.profile.is_buro = True
self.bda_user.profile.save()
def bda_participants(self):
"""The BdA participants views can be queried"""
client = Client()
show = self.tirage.spectacle_set.first()
client.login(self.bda_user.username, "bda4ever")
tirage_resp = client.get("/bda/spectacles/{}".format(self.tirage.id))
show_resp = client.get(
"/bda/spectacles/{}/{}".format(self.tirage.id, show.id)
)
reminder_url = "/bda/mails-rappel/{}".format(show.id)
reminder_get_resp = client.get(reminder_url)
reminder_post_resp = client.post(reminder_url)
self.assertEqual(200, tirage_resp.status_code)
self.assertEqual(200, show_resp.status_code)
self.assertEqual(200, reminder_get_resp.status_code)
self.assertEqual(200, reminder_post_resp.status_code)
def test_catalogue(self): def test_catalogue(self):
"""Test the catalogue JSON API""" """Test the catalogue JSON API"""
client = Client() client = Client()
# The `list` hooh # The `list` hook
resp = client.get("/bda/catalogue/list") resp = client.get("/bda/catalogue/list")
self.assertJSONEqual( self.assertJSONEqual(
resp.content.decode("utf-8"), resp.content.decode("utf-8"),

View file

@ -44,7 +44,10 @@ urlpatterns = [
url(r'^revente-immediat/(?P<tirage_id>\d+)$', url(r'^revente-immediat/(?P<tirage_id>\d+)$',
views.revente_shotgun, views.revente_shotgun,
name="bda-shotgun"), name="bda-shotgun"),
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel), url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
views.send_rappel,
name="bda-rappels"
),
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles, url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
name='bda-descriptions'), name='bda-descriptions'),
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue, url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,

View file

@ -1,15 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from collections import defaultdict from collections import defaultdict
from functools import partial
import random import random
import hashlib import hashlib
import time import time
import json import json
from datetime import timedelta from datetime import timedelta
from custommail.shortcuts import ( from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
send_mass_custom_mail, send_custom_mail, render_custom_mail from custommail.models import CustomMail
)
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
@ -27,7 +25,7 @@ from django.views.generic.list import ListView
from gestioncof.decorators import cof_required, buro_required from gestioncof.decorators import cof_required, buro_required
from bda.models import ( from bda.models import (
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage, Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
SpectacleRevente, Salle, Quote, CategorieSpectacle SpectacleRevente, Salle, CategorieSpectacle
) )
from bda.algorithm import Algorithm from bda.algorithm import Algorithm
from bda.forms import ( from bda.forms import (
@ -305,7 +303,8 @@ def do_tirage(tirage_elt, token):
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues # On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
ChoixRevente = Participant.choicesrevente.through ChoixRevente = Participant.choicesrevente.through
# Suppression des reventes demandées/enregistrées (si le tirage est relancé) # Suppression des reventes demandées/enregistrées
# (si le tirage est relancé)
( (
ChoixRevente.objects ChoixRevente.objects
.filter(spectacle__tirage=tirage_elt) .filter(spectacle__tirage=tirage_elt)
@ -612,7 +611,7 @@ def spectacle(request, tirage_id, spectacle_id):
participants_info = sorted(participants.values(), participants_info = sorted(participants.values(),
key=lambda part: part['lastname']) key=lambda part: part['lastname'])
return render(request, "bda-participants.html", return render(request, "bda/participants.html",
{"spectacle": spectacle, "participants": participants_info}) {"spectacle": spectacle, "participants": participants_info})
@ -651,20 +650,24 @@ def unpaid(request, tirage_id):
def send_rappel(request, spectacle_id): def send_rappel(request, spectacle_id):
show = get_object_or_404(Spectacle, id=spectacle_id) show = get_object_or_404(Spectacle, id=spectacle_id)
# Mails d'exemples # Mails d'exemples
exemple_mail_1place = render_custom_mail('bda-rappel', { custommail = CustomMail.objects.get(shortname="bda-rappel")
exemple_mail_1place = custommail.render({
'member': request.user, 'member': request.user,
'show': show, 'show': show,
'nb_attr': 1 'nb_attr': 1
}) })
exemple_mail_2places = render_custom_mail('bda-rappel', { exemple_mail_2places = custommail.render({
'member': request.user, 'member': request.user,
'show': show, 'show': show,
'nb_attr': 2 'nb_attr': 2
}) })
# Contexte # Contexte
ctxt = {'show': show, ctxt = {
'exemple_mail_1place': exemple_mail_1place, 'show': show,
'exemple_mail_2places': exemple_mail_2places} 'exemple_mail_1place': exemple_mail_1place,
'exemple_mail_2places': exemple_mail_2places,
'custommail': custommail,
}
# Envoi confirmé # Envoi confirmé
if request.method == 'POST': if request.method == 'POST':
members = show.send_rappel() members = show.send_rappel()
@ -673,6 +676,14 @@ def send_rappel(request, spectacle_id):
# Demande de confirmation # Demande de confirmation
else: else:
ctxt['sent'] = False ctxt['sent'] = False
if show.rappel_sent:
messages.warning(
request,
"Attention, un mail de rappel pour ce spectale a déjà été "
"envoyé le {}".format(formats.localize(
timezone.template_localtime(show.rappel_sent)
))
)
return render(request, "bda/mails-rappel.html", ctxt) return render(request, "bda/mails-rappel.html", ctxt)

View file

@ -40,8 +40,9 @@ a {
background: transparent; background: transparent;
} }
div.empty-form {
padding-bottom: 2em;
}
a:hover { a:hover {
color: #444; color: #444;
@ -341,10 +342,12 @@ fieldset legend {
font-weight: 700; font-weight: 700;
font-size: large; font-size: large;
color:#DE826B; color:#DE826B;
padding-bottom: .5em;
} }
#main-content h4 { #main-content h4 {
color:#DE826B; color:#DE826B;
padding-bottom: .5em;
} }
#main-content h2, #main-content h2,