Rajoute l'envoi des mails en tâche de fond, pour ne plus faire planter le serveur

This commit is contained in:
Tom Hubrecht 2021-08-20 00:45:33 +02:00
parent 3e683cd87e
commit 2396c163bd
8 changed files with 114 additions and 23 deletions

View file

@ -0,0 +1,29 @@
# Generated by Django 3.2.6 on 2021-08-19 22:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("elections", "0031_alter_election_options"),
]
operations = [
migrations.AddField(
model_name="user",
name="has_valid_email",
field=models.BooleanField(
default=None, null=True, verbose_name="email valide"
),
),
migrations.AlterField(
model_name="election",
name="sent_mail",
field=models.BooleanField(
default=False,
null=True,
verbose_name="mail avec les identifiants envoyé",
),
),
]

View file

@ -50,7 +50,7 @@ class Election(models.Model):
) )
sent_mail = models.BooleanField( sent_mail = models.BooleanField(
_("mail avec les identifiants envoyé"), default=False _("mail avec les identifiants envoyé"), null=True, default=False
) )
created_by = models.ForeignKey( created_by = models.ForeignKey(
@ -232,6 +232,7 @@ class User(AbstractUser):
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
full_name = models.CharField(_("Nom et Prénom"), max_length=150, blank=True) full_name = models.CharField(_("Nom et Prénom"), max_length=150, blank=True)
has_valid_email = models.BooleanField(_("email valide"), null=True, default=None)
@property @property
def base_username(self): def base_username(self):

12
elections/tasks.py Normal file
View file

@ -0,0 +1,12 @@
from background_task import background
from .models import Election
from .utils import send_mail
@background(schedule=5)
def send_election_mail(election_pk, subject, body, reply_to):
election = Election.objects.get(pk=election_pk)
send_mail(election, subject, body, reply_to)
election.sent_mail = True
election.save(update_fields=["sent_mail"])

View file

@ -29,7 +29,7 @@
</div> </div>
<div class="level-right"> <div class="level-right">
{% if not election.sent_mail %} {% if election.sent_mail is False %}
<div class="level-item"> <div class="level-item">
<a class="button is-light is-outlined is-primary" href="{% url 'election.mail-voters' election.pk %}"> <a class="button is-light is-outlined is-primary" href="{% url 'election.mail-voters' election.pk %}">
<span class="icon"> <span class="icon">
@ -38,6 +38,24 @@
<span>{% trans "Envoyer le mail d'annonce" %}</span> <span>{% trans "Envoyer le mail d'annonce" %}</span>
</a> </a>
</div> </div>
{% elif election.sent_mail is None %}
<div class="level-item">
<span class="button is-light is-outlined is-warning">
<span class="icon">
<i class="fas fa-sync-alt"></i>
</span>
<span>{% trans "Mail en cours de distribution" %}</span>
</span>
</div>
{% else %}
<div class="level-item">
<span class="button is-light is-outlined is-success">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Mail envoyé" %}</span>
</span>
</div>
{% endif %} {% endif %}
<div class="level-item"> <div class="level-item">
@ -114,7 +132,18 @@
<tr> <tr>
<td>{{ v.base_username }}</td> <td>{{ v.base_username }}</td>
<td>{{ v.full_name }}</td> <td>{{ v.full_name }}</td>
<td>{{ v.email }}</td> <td>
{{ v.email }}
{% if v.has_valid_email %}
<span class="icon has-text-success is-pulled-right">
<i class="fas fa-check"></i>
</span>
{% elif v.has_valid_email is False %}
<span class="icon has-text-danger is-pulled-right">
<i class="fas fa-times"></i>
</span>
{% endif %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -1,5 +1,6 @@
import csv import csv
import io import io
import smtplib
import networkx as nx import networkx as nx
import numpy as np import numpy as np
@ -8,7 +9,7 @@ from networkx.algorithms.dag import ancestors, descendants
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import EmailMessage # , get_connection from django.core.mail import EmailMessage
from django.core.validators import validate_email from django.core.validators import validate_email
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
@ -383,13 +384,14 @@ def check_csv(csv_file):
return errors return errors
def send_mail(election, mail_form): def send_mail(election, subject, body, reply_to):
"""Envoie le mail d'annonce de l'élection avec identifiants et mot de passe """Envoie le mail d'annonce de l'élection avec identifiants et mot de passe
aux votant·e·s, le mdp est généré en même temps que le mail est envoyé. aux votant·e·s, le mdp est généré en même temps que le mail est envoyé.
""" """
User = get_user_model() User = get_user_model()
voters = list(election.registered_voters.all()) # On n'envoie le mail qu'aux personnes qui n'en n'ont pas déjà reçu un
voters = list(election.registered_voters.exclude(has_valid_email=True))
e_url = reverse("election.view", args=[election.id]) e_url = reverse("election.view", args=[election.id])
url = f"https://vote.eleves.ens.fr{e_url}" url = f"https://vote.eleves.ens.fr{e_url}"
messages = [] messages = []
@ -397,18 +399,28 @@ def send_mail(election, mail_form):
password = generate_password() password = generate_password()
v.password = make_password(password) v.password = make_password(password)
messages.append( messages.append(
EmailMessage( (
subject=mail_form.cleaned_data["objet"], EmailMessage(
body=mail_form.cleaned_data["message"].format( subject=subject,
full_name=v.full_name, body=body.format(
election_url=url, full_name=v.full_name,
username=v.base_username, election_url=url,
password=password, username=v.base_username,
password=password,
),
to=[v.email],
reply_to=[reply_to],
), ),
to=[v.email], v,
) )
) )
# get_connection(fail_silently=False).send_messages(messages)
for m in messages: for (m, v) in messages:
m.send() try:
User.objects.bulk_update(voters, ["password"]) m.send()
except smtplib.SMTPException:
v.has_valid_email = False
else:
v.has_valid_email = True
User.objects.bulk_update(voters, ["password", "has_valid_email"])

View file

@ -41,7 +41,8 @@ from .mixins import (
) )
from .models import Election, Option, Question, Vote from .models import Election, Option, Question, Vote
from .staticdefs import MAIL_VOTE_DELETED, MAIL_VOTERS, QUESTION_TYPES, VOTE_RULES from .staticdefs import MAIL_VOTE_DELETED, MAIL_VOTERS, QUESTION_TYPES, VOTE_RULES
from .utils import create_users, send_mail from .tasks import send_election_mail
from .utils import create_users
User = get_user_model() User = get_user_model()
@ -158,7 +159,7 @@ class ElectionUploadVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormVi
class ElectionMailVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView): class ElectionMailVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView):
model = Election model = Election
form_class = VoterMailForm form_class = VoterMailForm
success_message = _("Mail d'annonce envoyé avec succès !") success_message = _("Mail d'annonce en cours d'envoi !")
template_name = "elections/mail_voters.html" template_name = "elections/mail_voters.html"
def get_queryset(self): def get_queryset(self):
@ -181,9 +182,14 @@ class ElectionMailVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
self.object.sent_mail = True self.object.sent_mail = None
send_mail(self.object, form)
self.object.save() self.object.save()
send_election_mail(
election_pk=self.object.pk,
subject=form.cleaned_data["objet"],
body=form.cleaned_data["message"],
reply_to=self.request.user.email,
)
return super().form_valid(form) return super().form_valid(form)

View file

@ -53,6 +53,7 @@ INSTALLED_APPS = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"kadenios.apps.IgnoreSrcStaticFilesConfig", "kadenios.apps.IgnoreSrcStaticFilesConfig",
"background_task",
"shared", "shared",
"elections", "elections",
"faqs", "faqs",

View file

@ -1,7 +1,8 @@
django==3.2.* django==3.2.*
django-translated-fields==0.11.1 django-translated-fields==0.11.*
authens>=0.1b2 authens>=0.1b2
markdown markdown
numpy numpy
networkx networkx
python-csv python-csv
django-background-tasks