2020-12-23 11:19:30 +01:00
|
|
|
import csv
|
|
|
|
import io
|
2020-12-24 00:41:29 +01:00
|
|
|
import random
|
2020-12-23 11:19:30 +01:00
|
|
|
|
2020-12-24 00:41:29 +01:00
|
|
|
from django.contrib.auth.hashers import make_password
|
2020-12-23 16:28:38 +01:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-12-24 00:41:29 +01:00
|
|
|
from django.core.mail import EmailMessage, get_connection
|
2020-12-23 16:28:38 +01:00
|
|
|
from django.core.validators import validate_email
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
2021-03-19 16:10:25 +01:00
|
|
|
# #############################################################################
|
|
|
|
# Fonctions universelles
|
|
|
|
# #############################################################################
|
|
|
|
|
2021-03-19 11:48:38 +01:00
|
|
|
|
|
|
|
def choices_length(choices):
|
|
|
|
"""Renvoie la longueur maximale des choix de choices"""
|
|
|
|
m = 0
|
|
|
|
for c in choices:
|
|
|
|
m = max(m, len(c[0]))
|
|
|
|
return m
|
2020-12-24 00:41:29 +01:00
|
|
|
|
2020-12-23 11:19:30 +01:00
|
|
|
|
2021-03-19 16:10:25 +01:00
|
|
|
# #############################################################################
|
|
|
|
# Classes pour différencier les différents types de questions
|
|
|
|
# #############################################################################
|
|
|
|
|
|
|
|
|
2021-03-19 14:25:13 +01:00
|
|
|
class CastFunctions:
|
|
|
|
"""Classe pour enregistrer les votes"""
|
|
|
|
|
|
|
|
def cast_select(user, vote_form):
|
2021-03-19 16:08:02 +01:00
|
|
|
"""On enregistre un vote classique"""
|
2021-03-19 14:25:13 +01:00
|
|
|
selected, n_selected = [], []
|
|
|
|
for v in vote_form:
|
|
|
|
if v.cleaned_data["selected"]:
|
|
|
|
selected.append(v.instance)
|
|
|
|
else:
|
|
|
|
n_selected.append(v.instance)
|
|
|
|
|
|
|
|
user.votes.add(*selected)
|
|
|
|
user.votes.remove(*n_selected)
|
|
|
|
|
|
|
|
|
2021-03-19 16:08:02 +01:00
|
|
|
class TallyFunctions:
|
|
|
|
"""Classe pour gérer les dépouillements"""
|
|
|
|
|
|
|
|
def tally_select(question):
|
|
|
|
"""On dépouille un vote classique"""
|
|
|
|
from .models import Option
|
|
|
|
|
|
|
|
max_votes = 0
|
|
|
|
options = []
|
|
|
|
|
|
|
|
for o in question.options.prefetch_related("voters").all():
|
|
|
|
o.nb_votes = o.voters.count()
|
|
|
|
max_votes = max(max_votes, o.nb_votes)
|
|
|
|
options.append(o)
|
|
|
|
|
|
|
|
question.max_votes = max_votes
|
|
|
|
question.save()
|
|
|
|
|
|
|
|
Option.objects.bulk_update(options, ["nb_votes"])
|
|
|
|
|
|
|
|
|
2021-03-19 22:24:27 +01:00
|
|
|
class ValidateFunctions:
|
|
|
|
"""Classe pour valider les formsets selon le type de question"""
|
|
|
|
|
|
|
|
def always_true(vote_form):
|
|
|
|
"""Retourne True pour les votes sans validation particulière"""
|
|
|
|
return True
|
|
|
|
|
|
|
|
def unique_selected(vote_form):
|
|
|
|
"""Vérifie qu'une seule option est choisie"""
|
|
|
|
print("toto")
|
|
|
|
nb_selected = 0
|
|
|
|
for v in vote_form:
|
|
|
|
nb_selected += v.cleaned_data["selected"]
|
|
|
|
|
|
|
|
if nb_selected == 0:
|
|
|
|
vote_form._non_form_errors.append(
|
|
|
|
ValidationError(_("Vous devez sélectionnner une option."))
|
|
|
|
)
|
|
|
|
return False
|
|
|
|
elif nb_selected > 1:
|
|
|
|
vote_form._non_form_errors.append(
|
|
|
|
ValidationError(_("Vous ne pouvez pas sélectionner plus d'une option."))
|
|
|
|
)
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2021-03-19 16:10:25 +01:00
|
|
|
# #############################################################################
|
|
|
|
# Fonctions pour importer une liste de votant·e·s
|
|
|
|
# #############################################################################
|
|
|
|
|
|
|
|
|
2020-12-23 11:19:30 +01:00
|
|
|
def create_users(election, csv_file):
|
|
|
|
"""Crée les votant·e·s pour l'élection donnée, en remplissant les champs
|
|
|
|
`username`, `election` et `full_name`.
|
|
|
|
"""
|
2020-12-23 17:08:43 +01:00
|
|
|
dialect = csv.Sniffer().sniff(csv_file.readline().decode("utf-8"))
|
2020-12-23 11:19:30 +01:00
|
|
|
csv_file.seek(0)
|
|
|
|
reader = csv.reader(io.StringIO(csv_file.read().decode("utf-8")), dialect)
|
|
|
|
for (username, full_name, email) in reader:
|
|
|
|
election.registered_voters.create(
|
2020-12-23 18:04:39 +01:00
|
|
|
username=f"{election.id}__{username}", email=email, full_name=full_name
|
2020-12-23 11:19:30 +01:00
|
|
|
)
|
2020-12-23 16:28:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
def check_csv(csv_file):
|
|
|
|
"""Vérifie que le fichier donnant la liste de votant·e·s est bien formé"""
|
|
|
|
try:
|
2020-12-23 17:08:43 +01:00
|
|
|
dialect = csv.Sniffer().sniff(csv_file.readline().decode("utf-8"))
|
2020-12-23 16:28:38 +01:00
|
|
|
except csv.Error:
|
|
|
|
return [
|
|
|
|
_(
|
|
|
|
"Format invalide. Vérifiez que le fichier est bien formé (i.e. "
|
|
|
|
"chaque ligne de la forme 'login,nom,email')."
|
|
|
|
)
|
|
|
|
]
|
|
|
|
csv_file.seek(0)
|
|
|
|
reader = csv.reader(io.StringIO(csv_file.read().decode("utf-8")), dialect)
|
|
|
|
|
|
|
|
errors = []
|
2020-12-23 17:08:43 +01:00
|
|
|
users = {}
|
2020-12-23 16:28:38 +01:00
|
|
|
line_nb = 0
|
|
|
|
for line in reader:
|
|
|
|
line_nb += 1
|
|
|
|
if len(line) != 3:
|
|
|
|
errors.append(
|
|
|
|
_("La ligne {} n'a pas le bon nombre d'éléments.").format(line_nb)
|
|
|
|
)
|
|
|
|
else:
|
2020-12-23 17:08:43 +01:00
|
|
|
if line[0] == "":
|
2020-12-23 16:28:38 +01:00
|
|
|
errors.append(
|
2020-12-23 17:08:43 +01:00
|
|
|
_("Valeur manquante dans la ligne {} : 'login'.").format(line_nb)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
if line[0] in users:
|
|
|
|
errors.append(
|
|
|
|
_("Doublon dans les logins : lignes {} et {}.").format(
|
|
|
|
line_nb, users[line[0]]
|
|
|
|
)
|
2020-12-23 16:28:38 +01:00
|
|
|
)
|
2020-12-23 17:08:43 +01:00
|
|
|
else:
|
|
|
|
users[line[0]] = line_nb
|
|
|
|
if line[1] == "":
|
|
|
|
errors.append(
|
|
|
|
_("Valeur manquante dans la ligne {} : 'nom'.").format(line_nb)
|
2020-12-23 16:28:38 +01:00
|
|
|
)
|
|
|
|
try:
|
|
|
|
validate_email(line[2])
|
|
|
|
except ValidationError:
|
|
|
|
errors.append(
|
|
|
|
_("Adresse mail invalide à la ligne {} : '{}'.").format(
|
|
|
|
line_nb, line[2]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return errors
|
|
|
|
|
|
|
|
|
2020-12-24 00:41:29 +01:00
|
|
|
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):
|
2020-12-23 16:28:38 +01:00
|
|
|
"""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é.
|
|
|
|
"""
|
2021-03-19 11:48:38 +01:00
|
|
|
from .models import User
|
|
|
|
|
2020-12-24 00:41:29 +01:00
|
|
|
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,
|
2021-03-25 09:31:52 +01:00
|
|
|
username=v.get_username(),
|
2020-12-24 00:41:29 +01:00
|
|
|
password=password,
|
|
|
|
),
|
|
|
|
to=[v.email],
|
|
|
|
)
|
|
|
|
)
|
|
|
|
get_connection(fail_silently=False).send_messages(messages)
|
|
|
|
User.objects.bulk_update(voters, ["password"])
|