From bab2629236adb2d95679aadf7a6dbd321ee5c360 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 24 Dec 2020 00:41:29 +0100 Subject: [PATCH] =?UTF-8?q?Permet=20d'envoyer=20un=20mail=20=C3=A0=20tous?= =?UTF-8?q?=20les=20votant=C2=B7e=C2=B7s=20avec=20leurs=20identifiants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- elections/forms.py | 5 ++ .../migrations/0006_election_sent_mail.py | 20 ++++++++ elections/models.py | 4 ++ elections/staticdefs.py | 12 +++++ .../templates/elections/election_admin.html | 2 +- .../templates/elections/mail_voters.html | 51 +++++++++++++++++++ .../templates/elections/upload_voters.html | 43 ++++++++++++---- elections/urls.py | 5 ++ elections/utils.py | 38 +++++++++++++- elections/views.py | 31 ++++++++++- kadenios/settings_base.py | 5 ++ 11 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 elections/migrations/0006_election_sent_mail.py create mode 100644 elections/staticdefs.py create mode 100644 elections/templates/elections/mail_voters.html diff --git a/elections/forms.py b/elections/forms.py index 1c7ad44..b25e0ab 100644 --- a/elections/forms.py +++ b/elections/forms.py @@ -42,6 +42,11 @@ class UploadVotersForm(forms.Form): return csv_file +class VoterMailForm(forms.Form): + objet = forms.CharField() + message = forms.CharField(widget=forms.Textarea) + + class QuestionForm(forms.ModelForm): class Meta: model = Question diff --git a/elections/migrations/0006_election_sent_mail.py b/elections/migrations/0006_election_sent_mail.py new file mode 100644 index 0000000..3556431 --- /dev/null +++ b/elections/migrations/0006_election_sent_mail.py @@ -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é" + ), + ), + ] diff --git a/elections/models.py b/elections/models.py index 6e714ab..0bdaf03 100644 --- a/elections/models.py +++ b/elections/models.py @@ -20,6 +20,10 @@ class Election(models.Model): _("restreint le vote à une liste de personnes"), default=True ) + sent_mail = models.BooleanField( + _("mail avec les identifiants envoyé"), default=False + ) + created_by = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="elections_created", diff --git a/elections/staticdefs.py b/elections/staticdefs.py new file mode 100644 index 0000000..c35708c --- /dev/null +++ b/elections/staticdefs.py @@ -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" +) diff --git a/elections/templates/elections/election_admin.html b/elections/templates/elections/election_admin.html index 27554fd..2449f06 100644 --- a/elections/templates/elections/election_admin.html +++ b/elections/templates/elections/election_admin.html @@ -40,7 +40,7 @@ - {% trans "Importer une liste de votant·e·s" %} + {% trans "Gestion de la liste de votant·e·s" %} {% endif %} diff --git a/elections/templates/elections/mail_voters.html b/elections/templates/elections/mail_voters.html new file mode 100644 index 0000000..4f8b477 --- /dev/null +++ b/elections/templates/elections/mail_voters.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% load i18n %} + + +{% block content %} + +

{% trans "Composition du mail aux votant·e·s" %}

+
+ +
+
+ {% 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." %} +
+
+
+
+ {% trans "Rajoutez le message à envoyer aux électeurs entre 'Dear {full_name}' et l'url de l'élection." %} +
+
+ +
+
+
+ {% csrf_token %} + + {% include "forms/form.html" %} + +
+
+ +
+ + +
+
+
+
+ +{% endblock %} diff --git a/elections/templates/elections/upload_voters.html b/elections/templates/elections/upload_voters.html index db53a6d..6798bad 100644 --- a/elections/templates/elections/upload_voters.html +++ b/elections/templates/elections/upload_voters.html @@ -19,9 +19,39 @@ {% block content %} -

{% trans "Importer une liste de votant·e·s" %}

+
+
+
+

{% trans "Gestion de la liste de votant·e·s" %}

+
+
+ +
+ {% if not election.sent_mail %} + + {% endif %} + + +
+

+{# Si on a déjà envoyé le mail avec les identifiants, on ne peut plus changer la liste #} +{% if not election.sent_mail %}
{% 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 :

Login_1,Prénom/Nom_1,mail_1@machin.test
Login_2,Prénom/Nom_2,mail_2@bidule.test
...
" %} @@ -50,23 +80,16 @@ - {% trans "Enregistrer" %} + {% trans "Importer" %}
-

+{% endif %} {# Liste des votant·e·s #} {% if voters %} diff --git a/elections/urls.py b/elections/urls.py index 370b70c..2daf387 100644 --- a/elections/urls.py +++ b/elections/urls.py @@ -7,6 +7,11 @@ urlpatterns = [ path("create/", views.ElectionCreateView.as_view(), name="election.create"), path("created/", views.ElectionListView.as_view(), name="election.created"), path("admin/", views.ElectionAdminView.as_view(), name="election.admin"), + path( + "mail-voters/", + views.ElectionMailVotersView.as_view(), + name="election.mail-voters", + ), path( "upload-voters/", views.ElectionUploadVotersView.as_view(), diff --git a/elections/utils.py b/elections/utils.py index fea3789..8c59921 100644 --- a/elections/utils.py +++ b/elections/utils.py @@ -1,10 +1,15 @@ import csv import io +import random +from django.contrib.auth.hashers import make_password from django.core.exceptions import ValidationError +from django.core.mail import EmailMessage, get_connection from django.core.validators import validate_email from django.utils.translation import gettext_lazy as _ +from .models import User + def create_users(election, csv_file): """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 -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 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"]) diff --git a/elections/views.py b/elections/views.py index 98b26c8..0076354 100644 --- a/elections/views.py +++ b/elections/views.py @@ -22,10 +22,12 @@ from .forms import ( OptionFormSet, QuestionForm, UploadVotersForm, + VoterMailForm, ) from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin, OpenElectionOnlyMixin 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* @@ -116,6 +118,33 @@ class ElectionUploadVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormVi 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): model = Election template_name = "elections/election_list.html" diff --git a/kadenios/settings_base.py b/kadenios/settings_base.py index 7cb2b32..510de1d 100644 --- a/kadenios/settings_base.py +++ b/kadenios/settings_base.py @@ -109,6 +109,11 @@ USE_L10N = True USE_TZ = True +# Mail configuration + +DEFAULT_FROM_EMAIL = "Kadenios " + + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/