Compare commits
5 commits
main
...
thubrecht/
Author | SHA1 | Date | |
---|---|---|---|
3438b32749 | |||
867f8f86d6 | |||
d975792cde | |||
6d98f63f0b | |||
f4f5611eaf |
10 changed files with 146 additions and 24 deletions
29
elections/migrations/0030_auto_20210619_1845.py
Normal file
29
elections/migrations/0030_auto_20210619_1845.py
Normal 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é",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
12
elections/tasks.py
Normal 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()
|
|
@ -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>
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ("celery_app",)
|
17
kadenios/celery.py
Normal file
17
kadenios/celery.py
Normal 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}")
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue