Permet d'envoyer un mail à tous les votant·e·s avec leurs identifiants

This commit is contained in:
Tom Hubrecht 2020-12-24 00:41:29 +01:00
parent b44f150cf9
commit bab2629236
11 changed files with 202 additions and 14 deletions

View file

@ -42,6 +42,11 @@ class UploadVotersForm(forms.Form):
return csv_file return csv_file
class VoterMailForm(forms.Form):
objet = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
class QuestionForm(forms.ModelForm): class QuestionForm(forms.ModelForm):
class Meta: class Meta:
model = Question model = Question

View file

@ -0,0 +1,20 @@
# Generated by Django 2.2.17 on 2020-12-23 19:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("elections", "0005_user_full_name"),
]
operations = [
migrations.AddField(
model_name="election",
name="sent_mail",
field=models.BooleanField(
default=False, verbose_name="mail avec les identifiants envoyé"
),
),
]

View file

@ -20,6 +20,10 @@ class Election(models.Model):
_("restreint le vote à une liste de personnes"), default=True _("restreint le vote à une liste de personnes"), default=True
) )
sent_mail = models.BooleanField(
_("mail avec les identifiants envoyé"), default=False
)
created_by = models.ForeignKey( created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
related_name="elections_created", related_name="elections_created",

12
elections/staticdefs.py Normal file
View file

@ -0,0 +1,12 @@
MAIL_VOTERS = (
"Dear {full_name},\n"
"\n"
"\n"
"Election URL: {election_url}\n"
"\n"
"Your voter ID: {username}\n"
"Your password: {password}\n"
"\n"
"-- \n"
"Kadenios"
)

View file

@ -40,7 +40,7 @@
<span class="icon"> <span class="icon">
<i class="fas fa-file-import"></i> <i class="fas fa-file-import"></i>
</span> </span>
<span>{% trans "Importer une liste de votant·e·s" %}</span> <span>{% trans "Gestion de la liste de votant·e·s" %}</span>
</a> </a>
</div> </div>
{% endif %} {% endif %}

View file

@ -0,0 +1,51 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1 class="title">{% trans "Composition du mail aux votant·e·s" %}</h1>
<hr>
<div class="message is-warning">
<div class="message-body">
{% trans "Assurez-vous que la liste des votant·e·s est complète, en effet, une fois le mail envoyé, il ne sera plus possible de la modifier." %}
</div>
</div>
<div class="message is-primary">
<div class="message-body">
{% trans "Rajoutez le message à envoyer aux électeurs entre 'Dear {full_name}' et l'url de l'élection." %}
</div>
</div>
<div class="columns is-centered">
<div class="column is-two-thirds">
<form action="" method="post" enctype="multipart/form-data" id="import-voters">
{% csrf_token %}
{% include "forms/form.html" %}
<div class="field is-grouped is-centered">
<div class="control is-expanded">
<button class="button is-fullwidth is-outlined is-primary is-light">
<span class="icon is-small">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Envoyer" %}</span>
</button>
</div>
<div class="control">
<a class="button is-primary" href="{% url 'election.upload-voters' election.pk %}">
<span class="icon is-small">
<i class="fas fa-undo-alt"></i>
</span>
<span>{% trans "Retour" %}</span>
</a>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View file

@ -19,9 +19,39 @@
{% block content %} {% block content %}
<h1 class="title">{% trans "Importer une liste de votant·e·s" %}</h1> <div class="level">
<div class="level-left">
<div class="item-level">
<h1 class="title">{% trans "Gestion de la liste de votant·e·s" %}</h1>
</div>
</div>
<div class="level-right">
{% if not election.sent_mail %}
<div class="level-item">
<a class="button is-light is-outlined is-primary" href="{% url 'election.mail-voters' election.pk %}">
<span class="icon">
<i class="fas fa-envelope-open"></i>
</span>
<span>{% trans "Envoyer le mail d'annonce" %}</span>
</a>
</div>
{% endif %}
<div class="level-item">
<a class="button is-primary" href="{% url 'election.admin' election.pk %}">
<span class="icon is-small">
<i class="fas fa-undo-alt"></i>
</span>
<span>{% trans "Retour" %}</span>
</a>
</div>
</div>
</div>
<hr> <hr>
{# Si on a déjà envoyé le mail avec les identifiants, on ne peut plus changer la liste #}
{% if not election.sent_mail %}
<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>" %}
@ -50,23 +80,16 @@
<span class="icon is-small"> <span class="icon is-small">
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
</span> </span>
<span>{% trans "Enregistrer" %}</span> <span>{% trans "Importer" %}</span>
</button> </button>
</div> </div>
<div class="control">
<a class="button is-primary" href="{% url 'election.admin' election.pk %}">
<span class="icon is-small">
<i class="fas fa-undo-alt"></i>
</span>
<span>{% trans "Retour" %}</span>
</a>
</div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<br> <br>
{% endif %}
{# Liste des votant·e·s #} {# Liste des votant·e·s #}
{% if voters %} {% if voters %}

View file

@ -7,6 +7,11 @@ urlpatterns = [
path("create/", views.ElectionCreateView.as_view(), name="election.create"), path("create/", views.ElectionCreateView.as_view(), name="election.create"),
path("created/", views.ElectionListView.as_view(), name="election.created"), path("created/", views.ElectionListView.as_view(), name="election.created"),
path("admin/<int:pk>", views.ElectionAdminView.as_view(), name="election.admin"), path("admin/<int:pk>", views.ElectionAdminView.as_view(), name="election.admin"),
path(
"mail-voters/<int:pk>",
views.ElectionMailVotersView.as_view(),
name="election.mail-voters",
),
path( path(
"upload-voters/<int:pk>", "upload-voters/<int:pk>",
views.ElectionUploadVotersView.as_view(), views.ElectionUploadVotersView.as_view(),

View file

@ -1,10 +1,15 @@
import csv import csv
import io import io
import random
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.validators import validate_email from django.core.validators import validate_email
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import User
def create_users(election, csv_file): def create_users(election, csv_file):
"""Crée les votant·e·s pour l'élection donnée, en remplissant les champs """Crée les votant·e·s pour l'élection donnée, en remplissant les champs
@ -72,8 +77,37 @@ def check_csv(csv_file):
return errors return errors
def send_mail(election): def generate_password():
random.seed()
alphabet = "abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
password = ""
for i in range(15):
password += random.choice(alphabet)
return password
def send_mail(election, mail_form):
"""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é.
""" """
pass voters = list(election.registered_voters.all())
url = f"https://kadenios.eleves.ens.fr/elections/view/{election.id}"
messages = []
for v in voters:
password = generate_password()
v.password = make_password(password)
messages.append(
EmailMessage(
subject=mail_form.cleaned_data["objet"],
body=mail_form.cleaned_data["message"].format(
full_name=v.full_name,
election_url=url,
username=v.base_username,
password=password,
),
to=[v.email],
)
)
get_connection(fail_silently=False).send_messages(messages)
User.objects.bulk_update(voters, ["password"])

View file

@ -22,10 +22,12 @@ from .forms import (
OptionFormSet, OptionFormSet,
QuestionForm, QuestionForm,
UploadVotersForm, UploadVotersForm,
VoterMailForm,
) )
from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin, OpenElectionOnlyMixin from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin, OpenElectionOnlyMixin
from .models import Election, Option, Question from .models import Election, Option, Question
from .utils import create_users from .staticdefs import MAIL_VOTERS
from .utils import create_users, send_mail
# TODO: access control *everywhere* # TODO: access control *everywhere*
@ -116,6 +118,33 @@ class ElectionUploadVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormVi
return super().form_valid(form) return super().form_valid(form)
class ElectionMailVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView):
model = Election
form_class = VoterMailForm
success_message = _("Mail d'annonce envoyé avec succès !")
template_name = "elections/mail_voters.html"
def get_success_url(self):
return reverse("election.upload-voters", args=[self.object.pk])
def get_initial(self):
return {"objet": f"Vote : {self.object.name}", "message": MAIL_VOTERS}
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def form_valid(self, form):
self.object.sent_mail = True
send_mail(self.object, form)
self.object.save()
return super().form_valid(form)
class ElectionListView(CreatorOnlyMixin, ListView): class ElectionListView(CreatorOnlyMixin, ListView):
model = Election model = Election
template_name = "elections/election_list.html" template_name = "elections/election_list.html"

View file

@ -109,6 +109,11 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
# Mail configuration
DEFAULT_FROM_EMAIL = "Kadenios <klub-dev@ens.fr>"
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/ # https://docs.djangoproject.com/en/2.2/howto/static-files/