From bab2629236adb2d95679aadf7a6dbd321ee5c360 Mon Sep 17 00:00:00 2001
From: Tom Hubrecht <tom.hubrecht@ens.fr>
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 @@
         <span class="icon">
           <i class="fas fa-file-import"></i>
         </span>
-        <span>{% trans "Importer une liste de votant·e·s" %}</span>
+        <span>{% trans "Gestion de la liste de votant·e·s" %}</span>
       </a>
     </div>
     {% 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 %}
+
+<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 %}
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 %}
 
-<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>
 
+{# 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-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>" %}
@@ -50,23 +80,16 @@
             <span class="icon is-small">
               <i class="fas fa-check"></i>
             </span>
-            <span>{% trans "Enregistrer" %}</span>
+            <span>{% trans "Importer" %}</span>
           </button>
         </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>
     </form>
   </div>
 </div>
 <br>
+{% 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/<int:pk>", views.ElectionAdminView.as_view(), name="election.admin"),
+    path(
+        "mail-voters/<int:pk>",
+        views.ElectionMailVotersView.as_view(),
+        name="election.mail-voters",
+    ),
     path(
         "upload-voters/<int:pk>",
         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 <klub-dev@ens.fr>"
+
+
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/2.2/howto/static-files/