Compare commits

...

5 commits

Author SHA1 Message Date
3438b32749 Finish task 2021-06-20 12:53:55 +02:00
867f8f86d6 On rajoute le reply_to 2021-06-20 09:00:28 +02:00
d975792cde On n'utilise plus l'adresse de KDE 2021-06-20 08:54:11 +02:00
6d98f63f0b Begin sending mail in the background 2021-06-19 21:06:22 +02:00
f4f5611eaf Celery start 2021-06-19 17:10:59 +02:00
10 changed files with 146 additions and 24 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):

12
elections/tasks.py Normal file
View file

@ -0,0 +1,12 @@
from celery import shared_task
from .models import Election
from .utils import send_mail
@shared_task
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()

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">
@ -50,10 +68,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 +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
@ -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, 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 = []
@ -392,18 +394,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) # 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,17 @@ 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"],
"reply_to": self.request.user.email,
},
)
return super().form_valid(form) return super().form_valid(form)

View file

@ -0,0 +1,3 @@
from .celery import app as celery_app
__all__ = ("celery_app",)

17
kadenios/celery.py Normal file
View file

@ -0,0 +1,17 @@
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "kadenios.settings.local")
app = Celery("kadenios")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f"{'test'!r}")
return 3
# print(f"Request: {self.request!r}")

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",
"django_celery_results",
"shared", "shared",
"elections", "elections",
"faqs", "faqs",
@ -92,7 +93,14 @@ WSGI_APPLICATION = "kadenios.wsgi.application"
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
DEFAULT_FROM_EMAIL = "Kadenios <klub-dev@ens.fr>" DEFAULT_FROM_EMAIL = "Kadenios <kadenios@vote.eleves.ens.fr>"
# #############################################################################
# Paramètres de Celery
# #############################################################################
CELERY_RESULT_BACKEND = "django-db"
CELERY_CACHE_BACKEND = "default"
# ############################################################################# # #############################################################################
# Paramètres d'authentification # Paramètres d'authentification

View file

@ -1,4 +1,6 @@
django==3.2.* django==3.2.*
celery==5.1.*
django-celery-results
django-translated-fields==0.11.1 django-translated-fields==0.11.1
authens>=0.1b2 authens>=0.1b2
markdown markdown