2021-04-15 23:00:10 +02:00
|
|
|
import csv
|
|
|
|
|
2020-12-18 15:02:04 +01:00
|
|
|
from django.contrib import messages
|
2021-05-29 12:05:47 +02:00
|
|
|
from django.contrib.auth import get_user_model
|
2020-11-20 17:46:53 +01:00
|
|
|
from django.contrib.messages.views import SuccessMessageMixin
|
2021-03-31 13:16:10 +02:00
|
|
|
from django.core.mail import EmailMessage
|
|
|
|
from django.db import transaction
|
2021-04-15 23:00:10 +02:00
|
|
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
2020-12-18 00:19:18 +01:00
|
|
|
from django.urls import reverse
|
2020-12-19 16:57:28 +01:00
|
|
|
from django.utils import timezone
|
2020-12-19 20:58:38 +01:00
|
|
|
from django.utils.text import slugify
|
2020-12-18 00:19:18 +01:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2020-12-19 23:48:18 +01:00
|
|
|
from django.views.generic import (
|
|
|
|
CreateView,
|
|
|
|
DetailView,
|
2020-12-22 01:17:38 +01:00
|
|
|
FormView,
|
2020-12-19 23:48:18 +01:00
|
|
|
ListView,
|
|
|
|
UpdateView,
|
2021-04-15 23:00:10 +02:00
|
|
|
View,
|
2020-12-19 23:48:18 +01:00
|
|
|
)
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2021-09-16 16:43:27 +02:00
|
|
|
from shared.json import JsonCreateView, JsonDeleteView, JsonUpdateView
|
2021-08-21 22:58:01 +02:00
|
|
|
from shared.views import BackgroundUpdateView, TimeMixin
|
2021-05-30 14:20:24 +02:00
|
|
|
|
2020-12-22 01:17:38 +01:00
|
|
|
from .forms import (
|
2021-03-31 13:16:10 +02:00
|
|
|
DeleteVoteForm,
|
2020-12-22 01:17:38 +01:00
|
|
|
ElectionForm,
|
|
|
|
OptionForm,
|
|
|
|
QuestionForm,
|
|
|
|
UploadVotersForm,
|
2020-12-24 00:41:29 +01:00
|
|
|
VoterMailForm,
|
2020-12-22 01:17:38 +01:00
|
|
|
)
|
2021-03-20 20:21:48 +01:00
|
|
|
from .mixins import (
|
|
|
|
AdminOnlyMixin,
|
2021-03-27 12:46:39 +01:00
|
|
|
ClosedElectionMixin,
|
2021-03-20 20:21:48 +01:00
|
|
|
CreatorOnlyEditMixin,
|
|
|
|
CreatorOnlyMixin,
|
2021-03-29 00:21:41 +02:00
|
|
|
NotArchivedMixin,
|
2021-03-20 20:21:48 +01:00
|
|
|
OpenElectionOnlyMixin,
|
|
|
|
)
|
2021-05-29 12:05:47 +02:00
|
|
|
from .models import Election, Option, Question, Vote
|
2021-03-31 13:16:10 +02:00
|
|
|
from .staticdefs import MAIL_VOTE_DELETED, MAIL_VOTERS, QUESTION_TYPES, VOTE_RULES
|
2021-08-20 00:45:33 +02:00
|
|
|
from .tasks import send_election_mail
|
|
|
|
from .utils import create_users
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2021-05-29 12:05:47 +02:00
|
|
|
User = get_user_model()
|
|
|
|
|
2020-11-20 17:46:53 +01:00
|
|
|
# TODO: access control *everywhere*
|
|
|
|
|
2020-12-19 23:48:18 +01:00
|
|
|
# #############################################################################
|
|
|
|
# Administration Views
|
|
|
|
# #############################################################################
|
|
|
|
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2021-03-20 20:21:48 +01:00
|
|
|
class ElectionCreateView(AdminOnlyMixin, SuccessMessageMixin, CreateView):
|
2020-11-20 17:46:53 +01:00
|
|
|
model = Election
|
2020-12-20 02:32:26 +01:00
|
|
|
form_class = ElectionForm
|
2020-12-19 22:22:23 +01:00
|
|
|
success_message = _("Élection créée avec succès !")
|
2020-12-20 10:49:39 +01:00
|
|
|
template_name = "elections/election_create.html"
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2020-12-19 20:58:38 +01:00
|
|
|
def get_success_url(self):
|
|
|
|
return reverse("election.admin", args=[self.object.pk])
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
# We need to add the short name and the creator od the election
|
|
|
|
form.instance.short_name = slugify(
|
2020-12-19 22:22:23 +01:00
|
|
|
form.instance.start_date.strftime("%Y-%m-%d") + "_" + form.instance.name
|
2020-12-19 20:58:38 +01:00
|
|
|
)[:50]
|
|
|
|
# TODO: Change this if we modify the user model
|
|
|
|
form.instance.created_by = self.request.user
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info("Election created", data={"election": form.instance.get_data()})
|
2020-12-19 20:58:38 +01:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2021-09-16 16:43:27 +02:00
|
|
|
class ElectionDeleteView(CreatorOnlyMixin, BackgroundUpdateView):
|
|
|
|
model = Election
|
|
|
|
pattern_name = "election.list"
|
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
obj = self.get_object()
|
|
|
|
# On ne peut supprimer que les élections n'ayant pas eu de vote et dont
|
|
|
|
# le mail d'annonce n'a pas été fait
|
|
|
|
if obj.voters.exists() or obj.send_election_mail:
|
2022-01-12 11:30:46 +01:00
|
|
|
self.log_warn("Cannot delete election")
|
2021-09-16 16:43:27 +02:00
|
|
|
raise Http404
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
2022-01-12 11:30:46 +01:00
|
|
|
obj = self.get_object()
|
|
|
|
|
|
|
|
self.log_info("Election deleted", data={"election": obj.get_data()})
|
|
|
|
|
|
|
|
obj.delete()
|
2021-09-16 16:43:27 +02:00
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class ElectionAdminView(CreatorOnlyMixin, TimeMixin, DetailView):
|
2020-12-19 15:04:04 +01:00
|
|
|
model = Election
|
|
|
|
template_name = "elections/election_admin.html"
|
|
|
|
|
2020-12-22 01:17:38 +01:00
|
|
|
def get_next_url(self):
|
|
|
|
return reverse("election.view", args=[self.object.pk])
|
|
|
|
|
2020-12-19 16:57:28 +01:00
|
|
|
def get_context_data(self, **kwargs):
|
2021-03-19 11:48:38 +01:00
|
|
|
kwargs.update(
|
2021-04-15 17:01:17 +02:00
|
|
|
{
|
|
|
|
"question_types": QUESTION_TYPES,
|
|
|
|
"o_form": OptionForm,
|
|
|
|
"q_form": QuestionForm,
|
|
|
|
}
|
2021-03-19 11:48:38 +01:00
|
|
|
)
|
2020-12-19 16:57:28 +01:00
|
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
2020-12-19 15:04:04 +01:00
|
|
|
def get_queryset(self):
|
|
|
|
return super().get_queryset().prefetch_related("questions__options")
|
|
|
|
|
|
|
|
|
2021-06-17 21:42:00 +02:00
|
|
|
class ElectionSetVisibleView(CreatorOnlyMixin, BackgroundUpdateView):
|
2021-06-17 11:53:09 +02:00
|
|
|
model = Election
|
|
|
|
pattern_name = "election.admin"
|
|
|
|
success_message = _("Élection visible !")
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
self.election = self.get_object()
|
|
|
|
self.election.visible = True
|
|
|
|
self.election.save()
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info(
|
|
|
|
"Election set to visible", data={"election": self.election.get_data()}
|
|
|
|
)
|
|
|
|
|
2021-06-17 11:53:09 +02:00
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class ExportVotersView(CreatorOnlyMixin, View):
|
2021-04-15 23:00:10 +02:00
|
|
|
model = Election
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
response = HttpResponse(content_type="text/csv")
|
|
|
|
|
|
|
|
writer = csv.writer(response)
|
|
|
|
response["Content-Disposition"] = "attachment; filename=voters.csv"
|
|
|
|
writer.writerow(["Nom", "login"])
|
|
|
|
|
2022-01-12 11:30:46 +01:00
|
|
|
obj = self.get_object()
|
|
|
|
|
|
|
|
for v in obj.voters.all():
|
2021-06-17 11:53:09 +02:00
|
|
|
writer.writerow([v.full_name, v.base_username])
|
2021-04-15 23:00:10 +02:00
|
|
|
|
2022-01-12 11:30:46 +01:00
|
|
|
self.log_info("Voters exported", data={"election": obj.get_data()})
|
|
|
|
|
2021-04-15 23:00:10 +02:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
2020-12-23 11:19:30 +01:00
|
|
|
class ElectionUploadVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView):
|
2020-12-22 01:17:38 +01:00
|
|
|
model = Election
|
|
|
|
form_class = UploadVotersForm
|
2020-12-23 11:19:30 +01:00
|
|
|
success_message = _("Liste de votant·e·s importée avec succès !")
|
2021-08-23 10:52:17 +02:00
|
|
|
template_name = "elections/election_upload_voters.html"
|
2020-12-22 01:17:38 +01:00
|
|
|
|
2020-12-24 01:10:05 +01:00
|
|
|
def get_queryset(self):
|
|
|
|
# On ne peut ajouter une liste d'électeurs que sur une élection restreinte
|
|
|
|
return super().get_queryset().filter(restricted=True)
|
|
|
|
|
2020-12-23 11:19:30 +01:00
|
|
|
def get_success_url(self):
|
|
|
|
return reverse("election.upload-voters", args=[self.object.pk])
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
2020-12-23 17:09:04 +01:00
|
|
|
context["voters"] = self.object.registered_voters.all()
|
2020-12-23 11:19:30 +01:00
|
|
|
return context
|
|
|
|
|
2020-12-22 01:17:38 +01:00
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
2020-12-23 11:19:30 +01:00
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
|
|
|
return super().post(request, *args, **kwargs)
|
|
|
|
|
2020-12-22 01:17:38 +01:00
|
|
|
def form_valid(self, form):
|
2020-12-23 11:19:30 +01:00
|
|
|
# On crée les comptes nécessaires à l'élection, en supprimant ceux
|
|
|
|
# existant déjà pour ne pas avoir de doublons
|
|
|
|
self.object.registered_voters.all().delete()
|
|
|
|
create_users(self.object, form.cleaned_data["csv_file"])
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info("Voters imported", data={"election": self.object.get_data()})
|
|
|
|
|
2020-12-23 11:19:30 +01:00
|
|
|
return super().form_valid(form)
|
2020-12-22 01:17:38 +01:00
|
|
|
|
|
|
|
|
2020-12-24 00:41:29 +01:00
|
|
|
class ElectionMailVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView):
|
|
|
|
model = Election
|
|
|
|
form_class = VoterMailForm
|
2021-08-20 00:45:33 +02:00
|
|
|
success_message = _("Mail d'annonce en cours d'envoi !")
|
2021-08-23 10:52:17 +02:00
|
|
|
template_name = "elections/election_mail_voters.html"
|
2020-12-24 00:41:29 +01:00
|
|
|
|
2020-12-24 01:10:05 +01:00
|
|
|
def get_queryset(self):
|
|
|
|
# On ne peut envoyer un mail que sur une élection restreinte qui n'a pas
|
|
|
|
# déjà vu son mail envoyé
|
|
|
|
return super().get_queryset().filter(restricted=True, sent_mail=False)
|
|
|
|
|
2020-12-24 00:41:29 +01:00
|
|
|
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):
|
2021-08-20 00:45:33 +02:00
|
|
|
self.object.sent_mail = None
|
2020-12-24 00:41:29 +01:00
|
|
|
self.object.save()
|
2021-08-20 00:45:33 +02:00
|
|
|
send_election_mail(
|
|
|
|
election_pk=self.object.pk,
|
|
|
|
subject=form.cleaned_data["objet"],
|
|
|
|
body=form.cleaned_data["message"],
|
|
|
|
reply_to=self.request.user.email,
|
|
|
|
)
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info(
|
|
|
|
"Started sending e-mails", data={"election": self.object.get_data()}
|
|
|
|
)
|
|
|
|
|
2020-12-24 00:41:29 +01:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2020-12-20 02:32:26 +01:00
|
|
|
class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
|
2020-11-20 17:46:53 +01:00
|
|
|
model = Election
|
2020-12-20 02:32:26 +01:00
|
|
|
form_class = ElectionForm
|
2020-11-20 17:46:53 +01:00
|
|
|
success_message = _("Élection modifiée avec succès !")
|
2020-12-19 16:57:28 +01:00
|
|
|
template_name = "elections/election_update.html"
|
|
|
|
|
2021-05-26 20:12:25 +02:00
|
|
|
def get_form(self, form_class=None):
|
|
|
|
form = super().get_form(form_class)
|
2022-01-12 11:30:46 +01:00
|
|
|
if self.object.sent_mail or self.object.sent_mail is None:
|
2021-05-26 20:12:25 +02:00
|
|
|
form.fields["restricted"].disabled = True
|
|
|
|
return form
|
|
|
|
|
2020-12-19 16:57:28 +01:00
|
|
|
def get_success_url(self):
|
|
|
|
return reverse("election.admin", args=[self.object.pk])
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2021-05-26 20:12:25 +02:00
|
|
|
def form_valid(self, form):
|
|
|
|
# Si on ouvre l'élection à tout le monde, on supprime les votant·e·s
|
|
|
|
# pré-enregistré·e·s
|
|
|
|
if not form.cleaned_data["restricted"]:
|
|
|
|
self.object.registered_voters.all().delete()
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info("Updated election", data={"election": self.object.get_data()})
|
|
|
|
|
2021-05-26 20:12:25 +02:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2021-08-24 17:54:35 +02:00
|
|
|
class DeleteVoteView(ClosedElectionMixin, JsonDeleteView):
|
|
|
|
model = Election
|
|
|
|
|
|
|
|
def get_message(self):
|
|
|
|
return {
|
|
|
|
"content": _("Vote de {} supprimé !").format(self.voter.full_name),
|
|
|
|
"class": "success",
|
|
|
|
}
|
|
|
|
|
|
|
|
@transaction.atomic
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
election = self.get_object()
|
|
|
|
self.voter = User.objects.get(pk=self.kwargs["user_pk"])
|
|
|
|
|
|
|
|
# On envoie un mail à la personne lui indiquant que le vote est supprimé
|
|
|
|
EmailMessage(
|
|
|
|
subject="Vote removed",
|
|
|
|
body=MAIL_VOTE_DELETED.format(
|
|
|
|
full_name=self.voter.full_name,
|
|
|
|
election_name=election.name,
|
|
|
|
),
|
|
|
|
to=[self.voter.email],
|
|
|
|
).send()
|
|
|
|
|
|
|
|
# On supprime les votes
|
|
|
|
Vote.objects.filter(
|
|
|
|
user=self.voter,
|
|
|
|
option__question__election=election,
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
# On marque les questions comme non votées
|
|
|
|
self.voter.cast_elections.remove(election)
|
|
|
|
self.voter.cast_questions.remove(*list(election.questions.all()))
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_warn(
|
|
|
|
"Vote deleted",
|
|
|
|
data={"election": election.get_data(), "voter": self.voter.get_data()},
|
|
|
|
)
|
|
|
|
|
2021-08-24 17:54:35 +02:00
|
|
|
return self.render_to_json(action="delete")
|
|
|
|
|
|
|
|
|
2021-03-27 12:46:39 +01:00
|
|
|
class ElectionTallyView(ClosedElectionMixin, BackgroundUpdateView):
|
2020-12-19 16:57:28 +01:00
|
|
|
model = Election
|
2020-12-19 18:26:25 +01:00
|
|
|
pattern_name = "election.admin"
|
2020-12-20 02:32:26 +01:00
|
|
|
success_message = _("Élection dépouillée avec succès !")
|
2020-12-19 18:26:25 +01:00
|
|
|
|
|
|
|
def get_queryset(self):
|
2020-12-19 22:22:23 +01:00
|
|
|
return (
|
|
|
|
super()
|
|
|
|
.get_queryset()
|
2021-03-27 12:46:39 +01:00
|
|
|
.filter(tallied=False)
|
2020-12-19 22:22:23 +01:00
|
|
|
.prefetch_related("questions__options")
|
|
|
|
)
|
2020-12-19 16:57:28 +01:00
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
election = self.get_object()
|
2020-12-19 18:26:25 +01:00
|
|
|
for q in election.questions.all():
|
2021-03-19 16:08:02 +01:00
|
|
|
q.tally()
|
2020-12-19 18:26:25 +01:00
|
|
|
|
|
|
|
election.tallied = True
|
2021-06-28 22:43:35 +02:00
|
|
|
election.time_tallied = timezone.now()
|
2020-12-19 18:26:25 +01:00
|
|
|
election.save()
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info("Election tallied", data={"election": election.get_data()})
|
|
|
|
|
2020-12-19 16:57:28 +01:00
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2021-03-27 12:46:39 +01:00
|
|
|
class ElectionChangePublicationView(ClosedElectionMixin, BackgroundUpdateView):
|
2020-12-19 16:57:28 +01:00
|
|
|
model = Election
|
2020-12-19 18:26:25 +01:00
|
|
|
pattern_name = "election.admin"
|
2020-12-19 16:57:28 +01:00
|
|
|
|
2020-12-20 02:32:26 +01:00
|
|
|
def get_success_message(self):
|
|
|
|
if self.election.results_public:
|
|
|
|
return _("Élection publiée avec succès !")
|
|
|
|
return _("Élection dépubliée avec succès !")
|
|
|
|
|
2020-12-19 16:57:28 +01:00
|
|
|
def get(self, request, *args, **kwargs):
|
2020-12-20 02:32:26 +01:00
|
|
|
self.election = self.get_object()
|
|
|
|
self.election.results_public = not self.election.results_public
|
2021-06-28 22:43:35 +02:00
|
|
|
self.election.time_published = (
|
|
|
|
timezone.now() if self.election.results_public else None
|
|
|
|
)
|
|
|
|
|
2020-12-20 02:32:26 +01:00
|
|
|
self.election.save()
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info(
|
|
|
|
"Election published"
|
|
|
|
if self.election.results_public
|
|
|
|
else "Election unpublished",
|
|
|
|
data={"election": self.election.get_data()},
|
|
|
|
)
|
|
|
|
|
2020-12-19 16:57:28 +01:00
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2021-06-17 11:53:09 +02:00
|
|
|
class DownloadResultsView(CreatorOnlyMixin, View):
|
2021-06-10 21:13:48 +02:00
|
|
|
model = Election
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
return super().get_queryset().filter(tallied=True)
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
2022-01-12 11:30:46 +01:00
|
|
|
obj = self.get_object()
|
|
|
|
|
|
|
|
content = "\n".join([q.results for q in obj.questions.all()])
|
2021-06-10 21:13:48 +02:00
|
|
|
|
|
|
|
response = HttpResponse(content, content_type="text/plain")
|
|
|
|
response["Content-Disposition"] = "attachment; filename=results.txt"
|
|
|
|
|
2022-01-12 11:30:46 +01:00
|
|
|
self.log_info("Results downloaded", data={"election": obj.get_data()})
|
|
|
|
|
2021-06-10 21:13:48 +02:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
2021-03-27 12:46:39 +01:00
|
|
|
class ElectionArchiveView(ClosedElectionMixin, BackgroundUpdateView):
|
2020-12-19 16:57:28 +01:00
|
|
|
model = Election
|
2020-12-19 18:26:25 +01:00
|
|
|
pattern_name = "election.admin"
|
2020-12-20 02:32:26 +01:00
|
|
|
success_message = _("Élection archivée avec succès !")
|
2020-12-19 16:57:28 +01:00
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
election = self.get_object()
|
2020-12-19 18:26:25 +01:00
|
|
|
election.archived = True
|
|
|
|
election.save()
|
2022-01-12 11:30:46 +01:00
|
|
|
|
|
|
|
self.log_info("Election archived", data={"election": election.get_data()})
|
|
|
|
|
2020-12-19 16:57:28 +01:00
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
2020-12-20 02:32:26 +01:00
|
|
|
# #############################################################################
|
|
|
|
# Question Views
|
|
|
|
# #############################################################################
|
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class CreateQuestionView(CreatorOnlyEditMixin, TimeMixin, JsonCreateView):
|
2020-12-20 02:32:26 +01:00
|
|
|
model = Election
|
|
|
|
form_class = QuestionForm
|
2021-08-21 22:58:01 +02:00
|
|
|
context_object_name = "q"
|
|
|
|
template_name = "elections/admin/question.html"
|
2020-12-20 02:32:26 +01:00
|
|
|
|
|
|
|
def form_valid(self, form):
|
2021-08-21 22:58:01 +02:00
|
|
|
form.instance.election = self.get_object()
|
2020-12-20 02:32:26 +01:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class UpdateQuestionView(CreatorOnlyEditMixin, TimeMixin, JsonUpdateView):
|
2020-12-20 02:32:26 +01:00
|
|
|
model = Question
|
|
|
|
form_class = QuestionForm
|
2021-08-21 22:58:01 +02:00
|
|
|
context_object_name = "q"
|
|
|
|
template_name = "elections/admin/question.html"
|
2020-12-20 02:32:26 +01:00
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class DeleteQuestionView(CreatorOnlyEditMixin, JsonDeleteView):
|
2020-12-20 02:32:26 +01:00
|
|
|
model = Question
|
2021-08-21 22:58:01 +02:00
|
|
|
message = _("Question supprimée !")
|
2020-12-20 02:32:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
# #############################################################################
|
|
|
|
# Option Views
|
|
|
|
# #############################################################################
|
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class CreateOptionView(CreatorOnlyEditMixin, TimeMixin, JsonCreateView):
|
2020-12-20 02:32:26 +01:00
|
|
|
model = Question
|
|
|
|
form_class = OptionForm
|
2021-08-21 22:58:01 +02:00
|
|
|
context_object_name = "o"
|
|
|
|
template_name = "elections/admin/option.html"
|
2020-12-20 02:32:26 +01:00
|
|
|
|
|
|
|
def form_valid(self, form):
|
2021-08-21 22:58:01 +02:00
|
|
|
form.instance.question = self.get_object()
|
2020-12-20 02:32:26 +01:00
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class UpdateOptionView(CreatorOnlyEditMixin, TimeMixin, JsonUpdateView):
|
2020-12-20 02:32:26 +01:00
|
|
|
model = Option
|
|
|
|
form_class = OptionForm
|
2021-08-21 22:58:01 +02:00
|
|
|
context_object_name = "o"
|
|
|
|
template_name = "elections/admin/option.html"
|
2020-12-20 02:32:26 +01:00
|
|
|
|
|
|
|
|
2021-08-21 22:58:01 +02:00
|
|
|
class DeleteOptionView(CreatorOnlyEditMixin, JsonDeleteView):
|
2020-12-20 02:32:26 +01:00
|
|
|
model = Option
|
2021-08-21 22:58:01 +02:00
|
|
|
message = _("Option supprimée !")
|
2020-12-20 02:32:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
# #############################################################################
|
2020-12-24 01:41:29 +01:00
|
|
|
# Public Views
|
2020-12-20 02:32:26 +01:00
|
|
|
# #############################################################################
|
|
|
|
|
|
|
|
|
2021-03-29 00:21:41 +02:00
|
|
|
class ElectionListView(NotArchivedMixin, ListView):
|
2021-03-20 12:25:01 +01:00
|
|
|
model = Election
|
|
|
|
template_name = "elections/election_list.html"
|
|
|
|
|
|
|
|
|
2021-03-29 00:21:41 +02:00
|
|
|
class ElectionView(NotArchivedMixin, DetailView):
|
2020-11-20 17:46:53 +01:00
|
|
|
model = Election
|
2020-12-18 00:19:18 +01:00
|
|
|
template_name = "elections/election.html"
|
2020-11-20 17:46:53 +01:00
|
|
|
|
2020-12-21 00:07:07 +01:00
|
|
|
def get_next_url(self):
|
|
|
|
return self.request.path
|
|
|
|
|
2020-11-20 17:46:53 +01:00
|
|
|
def get_context_data(self, **kwargs):
|
2020-12-21 00:07:07 +01:00
|
|
|
user = self.request.user
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context["current_time"] = timezone.now()
|
2021-03-20 09:35:16 +01:00
|
|
|
|
|
|
|
if user.is_authenticated:
|
|
|
|
context["can_vote"] = user.can_vote(self.request, context["election"])
|
|
|
|
context["cast_questions"] = user.cast_questions.all()
|
2021-03-20 10:07:06 +01:00
|
|
|
context["has_voted"] = user.cast_elections.filter(
|
|
|
|
pk=context["election"].pk
|
|
|
|
).exists()
|
2021-03-20 09:35:16 +01:00
|
|
|
|
2020-12-21 00:07:07 +01:00
|
|
|
return context
|
2020-12-18 00:19:18 +01:00
|
|
|
|
|
|
|
def get_queryset(self):
|
2021-03-29 20:35:34 +02:00
|
|
|
return (
|
|
|
|
super()
|
|
|
|
.get_queryset()
|
2021-04-12 09:52:17 +02:00
|
|
|
.select_related("created_by")
|
2021-03-29 20:35:34 +02:00
|
|
|
.prefetch_related("questions__options", "questions__duels")
|
|
|
|
)
|
2020-12-18 00:19:18 +01:00
|
|
|
|
2020-12-20 10:35:18 +01:00
|
|
|
|
2021-03-29 00:21:41 +02:00
|
|
|
class ElectionVotersView(NotArchivedMixin, DetailView):
|
2020-12-24 01:41:29 +01:00
|
|
|
model = Election
|
|
|
|
template_name = "elections/election_voters.html"
|
|
|
|
|
2021-03-31 13:16:10 +02:00
|
|
|
def get_context_data(self, **kwargs):
|
2021-03-31 20:27:23 +02:00
|
|
|
user = self.request.user
|
2021-03-31 13:16:10 +02:00
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
election = context["election"]
|
2021-04-09 11:40:16 +02:00
|
|
|
voters = list(election.voters.all())
|
2021-03-31 20:27:23 +02:00
|
|
|
|
|
|
|
if user.is_authenticated:
|
2022-04-02 21:24:23 +02:00
|
|
|
context["can_vote"] = user.can_vote(self.request, context["election"])
|
|
|
|
context["is_admin"] = user.is_admin(election)
|
2021-04-09 11:40:16 +02:00
|
|
|
can_delete = (
|
2021-08-24 17:54:35 +02:00
|
|
|
election.created_by == user
|
2021-03-31 20:27:23 +02:00
|
|
|
and election.end_date < timezone.now()
|
|
|
|
and not election.tallied
|
|
|
|
)
|
2021-04-09 11:40:16 +02:00
|
|
|
if can_delete:
|
2021-05-27 19:54:39 +02:00
|
|
|
context["d_form"] = DeleteVoteForm()
|
2021-04-09 11:40:16 +02:00
|
|
|
|
|
|
|
context["can_delete"] = can_delete
|
2021-08-25 15:59:55 +02:00
|
|
|
context["from_admin"] = self.request.GET.get("prev") == "admin"
|
2021-04-12 14:29:23 +02:00
|
|
|
context["voters"] = voters
|
|
|
|
|
2021-03-31 13:16:10 +02:00
|
|
|
return context
|
|
|
|
|
2020-12-24 01:41:29 +01:00
|
|
|
|
2021-04-17 00:23:33 +02:00
|
|
|
class ElectionBallotsView(NotArchivedMixin, DetailView):
|
|
|
|
model = Election
|
|
|
|
template_name = "elections/election_ballots.html"
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
return (
|
|
|
|
super()
|
|
|
|
.get_queryset()
|
2021-07-10 20:05:09 +02:00
|
|
|
.filter(results_public=True, tallied=True)
|
2021-04-17 00:23:33 +02:00
|
|
|
.prefetch_related("questions__options")
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-12-20 18:50:38 +01:00
|
|
|
class VoteView(OpenElectionOnlyMixin, DetailView):
|
2020-12-20 10:35:18 +01:00
|
|
|
model = Question
|
|
|
|
|
2021-04-12 14:23:45 +02:00
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
# Si l'utilisateur n'est pas connecté on renvoie sur la vue de l'élection
|
|
|
|
if not request.user.is_authenticated:
|
|
|
|
return HttpResponseRedirect(
|
|
|
|
reverse("election.view", args=[super().get_object().election.pk])
|
|
|
|
)
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
2021-04-16 11:01:54 +02:00
|
|
|
def get_template_names(self):
|
|
|
|
return [f"elections/vote/{self.object.vote_type}.html"]
|
|
|
|
|
2020-12-22 01:17:38 +01:00
|
|
|
def get_next_url(self):
|
2020-12-21 00:07:07 +01:00
|
|
|
return reverse("election.view", args=[self.object.election.pk])
|
|
|
|
|
2020-12-20 10:49:39 +01:00
|
|
|
def get_success_url(self):
|
|
|
|
questions = list(self.object.election.questions.all())
|
|
|
|
q_index = questions.index(self.object)
|
|
|
|
if q_index + 1 == len(questions):
|
|
|
|
# On était à la dernière question
|
2021-03-19 14:25:13 +01:00
|
|
|
# On enregistre le vote pour l'élection
|
|
|
|
self.object.election.voters.add(self.request.user)
|
2020-12-20 10:49:39 +01:00
|
|
|
return reverse("election.view", args=[self.object.election.pk])
|
|
|
|
|
|
|
|
# On récupère l'id de la prochaine question
|
|
|
|
q_next = questions[q_index + 1].pk
|
|
|
|
|
|
|
|
return reverse("election.vote", args=[q_next])
|
|
|
|
|
2021-03-19 10:57:36 +01:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
questions = list(self.object.election.questions.all())
|
|
|
|
context["q_index"] = questions.index(self.object) + 1
|
|
|
|
context["q_total"] = len(questions)
|
2021-04-12 03:07:31 +02:00
|
|
|
context["nb_options"] = self.object.options.count()
|
2021-04-20 10:00:03 +02:00
|
|
|
context["range_options"] = range(1, context["nb_options"] + 1)
|
2021-04-12 21:07:39 +02:00
|
|
|
context["vote_rule"] = VOTE_RULES[self.object.type].format(**context)
|
2021-03-19 10:57:36 +01:00
|
|
|
return context
|
|
|
|
|
2020-12-20 18:50:38 +01:00
|
|
|
def get_object(self):
|
|
|
|
question = super().get_object()
|
|
|
|
# Seulement les utilisateur·ice·s ayant le droit de voter dans l'élection
|
|
|
|
# peuvent voir la page
|
2021-01-27 20:54:23 +01:00
|
|
|
if not self.request.user.can_vote(self.request, question.election):
|
2020-12-20 18:50:38 +01:00
|
|
|
raise Http404
|
|
|
|
return question
|
|
|
|
|
2020-12-18 00:19:18 +01:00
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
2021-03-29 12:42:34 +02:00
|
|
|
vote_form = self.object.get_formset()(instance=self.object)
|
2020-12-18 00:19:18 +01:00
|
|
|
|
2020-12-18 17:38:44 +01:00
|
|
|
return self.render_to_response(self.get_context_data(formset=vote_form))
|
2020-12-18 00:19:18 +01:00
|
|
|
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
2021-03-29 12:42:34 +02:00
|
|
|
vote_form = self.object.get_formset()(self.request.POST, instance=self.object)
|
2020-12-18 00:19:18 +01:00
|
|
|
|
2021-03-19 22:24:27 +01:00
|
|
|
if self.object.is_form_valid(vote_form):
|
|
|
|
# On enregistre le vote
|
|
|
|
self.object.cast_ballot(self.request.user, vote_form)
|
|
|
|
self.object.voters.add(self.request.user)
|
|
|
|
messages.success(self.request, _("Votre choix a bien été enregistré !"))
|
|
|
|
|
|
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
else:
|
|
|
|
return self.render_to_response(self.get_context_data(formset=vote_form))
|