Begin sending mail in the background

This commit is contained in:
Tom Hubrecht 2021-06-19 21:06:22 +02:00
parent f4f5611eaf
commit 6d98f63f0b
7 changed files with 108 additions and 26 deletions

View file

@ -0,0 +1,29 @@
# Generated by Django 3.2.4 on 2021-06-19 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("elections", "0029_alter_election_visible"),
]
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(
@ -222,6 +222,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):

View file

@ -1,2 +1,13 @@
def send_election_mail(election): from celery import shared_task
pass
from .models import Election
from .utils import send_mail
@shared_task
def send_election_mail(election_pk, subject, body):
election = Election.objects.get(pk=election_pk)
send_mail(election, subject, body)
election.sent_mail = False
election.save()
# election.sent_mail = True

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,15 @@
<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>
{% endif %} {% endif %}
<div class="level-item"> <div class="level-item">
@ -50,10 +59,10 @@
</div> </div>
</div> </div>
</div> </div>
<hr>
{# Si on a déjà envoyé le mail avec les identifiants, on ne peut plus changer la liste #} {# Si on a déjà envoyé le mail avec les identifiants, on ne peut plus changer la liste #}
{% if not election.sent_mail %} {% if election.sent_mail is False %}
<hr>
<div class="message is-warning"> <div class="message is-warning">
<div class="message-body"> <div class="message-body">
{% trans "Importez un fichier au format CSV, avec sur la première colonne le login, sur la deuxième, le nom et prénom et enfin l'adresse email sur la troisième. Soit :<br><br><pre>Login_1,Prénom/Nom_1,mail_1@machin.test<br>Login_2,Prénom/Nom_2,mail_2@bidule.test<br>...</pre>" %} {% trans "Importez un fichier au format CSV, avec sur la première colonne le login, sur la deuxième, le nom et prénom et enfin l'adresse email sur la troisième. Soit :<br><br><pre>Login_1,Prénom/Nom_1,mail_1@machin.test<br>Login_2,Prénom/Nom_2,mail_2@bidule.test<br>...</pre>" %}
@ -114,7 +123,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
@ -378,13 +379,14 @@ def check_csv(csv_file):
return errors return errors
def send_mail(election, mail_form): def send_mail(election, subject, body):
"""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 = []
@ -392,18 +394,27 @@ 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],
), ),
to=[v.email], v,
) )
) )
# get_connection(fail_silently=False).send_messages(messages) # 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,16 @@ 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.apply_async(
countdown=5,
kwargs={
"election_pk": self.object.pk,
"subject": form.cleaned_data["objet"],
"body": form.cleaned_data["message"],
},
)
return super().form_valid(form) return super().form_valid(form)

View file

@ -12,4 +12,6 @@ app.autodiscover_tasks()
@app.task(bind=True) @app.task(bind=True)
def debug_task(self): def debug_task(self):
print(f"Request: {self.request!r}") print(f"{'test'!r}")
return 3
# print(f"Request: {self.request!r}")