kadenios/elections/views.py

332 lines
11 KiB
Python

from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
# from django.db.models import Count, Prefetch
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
CreateView,
DetailView,
ListView,
RedirectView,
UpdateView,
)
from .forms import ElectionForm, OptionForm, OptionFormSet, QuestionForm
from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin, OpenElectionOnlyMixin
from .models import Election, Option, Question
# TODO: access control *everywhere*
# #############################################################################
# Utils Views
# #############################################################################
class BackgroundUpdateView(RedirectView):
success_message = ""
def get_success_message(self):
return self.success_message
def get(self, request, *args, **kwargs):
success_message = self.get_success_message()
if success_message:
messages.success(self.request, success_message)
return super().get(self, request, *args, **kwargs)
# #############################################################################
# Administration Views
# #############################################################################
class ElectionCreateView(SuccessMessageMixin, CreateView):
model = Election
form_class = ElectionForm
success_message = _("Élection créée avec succès !")
template_name = "elections/election_create.html"
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(
form.instance.start_date.strftime("%Y-%m-%d") + "_" + form.instance.name
)[:50]
# TODO: Change this if we modify the user model
form.instance.created_by = self.request.user
return super().form_valid(form)
# TODO : only the creator can edit the election and view the admin panel
class ElectionAdminView(CreatorOnlyMixin, DetailView):
model = Election
template_name = "elections/election_admin.html"
def get_context_data(self, **kwargs):
kwargs.update({"current_time": timezone.now()})
return super().get_context_data(**kwargs)
def get_queryset(self):
return super().get_queryset().prefetch_related("questions__options")
class ElectionListView(CreatorOnlyMixin, ListView):
model = Election
template_name = "elections/election_list.html"
class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Election
form_class = ElectionForm
success_message = _("Élection modifiée avec succès !")
template_name = "elections/election_update.html"
def get_success_url(self):
return reverse("election.admin", args=[self.object.pk])
class ElectionTallyView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election
pattern_name = "election.admin"
success_message = _("Élection dépouillée avec succès !")
def get_queryset(self):
return (
super()
.get_queryset()
.filter(end_date__lt=timezone.now())
.prefetch_related("questions__options")
)
def get(self, request, *args, **kwargs):
election = self.get_object()
options, questions = [], []
for q in election.questions.all():
max_votes = 0
for o in q.options.all():
o.nb_votes = o.voters.count()
max_votes = max(max_votes, o.nb_votes)
options.append(o)
q.max_votes = max_votes
questions.append(q)
Option.objects.bulk_update(options, ["nb_votes"])
Question.objects.bulk_update(questions, ["max_votes"])
election.tallied = True
election.save()
return super().get(request, *args, **kwargs)
class ElectionChangePublicationView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election
pattern_name = "election.admin"
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 !")
def get_queryset(self):
return super().get_queryset().filter(end_date__lt=timezone.now())
def get(self, request, *args, **kwargs):
self.election = self.get_object()
self.election.results_public = not self.election.results_public
self.election.save()
return super().get(request, *args, **kwargs)
class ElectionArchiveView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Election
pattern_name = "election.admin"
success_message = _("Élection archivée avec succès !")
def get_queryset(self):
return super().get_queryset().filter(end_date__lt=timezone.now())
def get(self, request, *args, **kwargs):
election = self.get_object()
election.archived = True
election.save()
return super().get(request, *args, **kwargs)
# #############################################################################
# Question Views
# #############################################################################
class AddQuestionView(CreatorOnlyEditMixin, CreateView):
model = Election
form_class = QuestionForm
def get_success_url(self):
return reverse("election.admin", args=[self.election.pk]) + "#q_add"
def form_valid(self, form):
self.election = self.get_object()
# On ajoute l'élection voulue à la question créée
form.instance.election = self.election
return super().form_valid(form)
class ModQuestionView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Question
form_class = QuestionForm
success_message = _("Question modifiée avec succès !")
template_name = "elections/question_update.html"
def get_success_url(self):
return (
reverse("election.admin", args=[self.object.election.pk])
+ f"#q_{self.object.pk}"
)
class DelQuestionView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Question
success_message = _("Question supprimée !")
def get_redirect_url(self, *args, **kwargs):
return reverse("election.admin", args=[self.election.pk]) + "#q_add"
def get(self, request, *args, **kwargs):
question = self.get_object()
self.election = question.election
question.delete()
return super().get(request, *args, **kwargs)
# #############################################################################
# Option Views
# #############################################################################
class AddOptionView(CreatorOnlyEditMixin, CreateView):
model = Question
form_class = OptionForm
def get_queryset(self):
return super().get_queryset()
def get_success_url(self):
return (
reverse("election.admin", args=[self.question.election.pk])
+ f"#q_{self.question.pk}"
)
def form_valid(self, form):
self.question = self.get_object()
# On ajoute l'élection voulue à la question créée
form.instance.question = self.question
return super().form_valid(form)
class ModOptionView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
model = Option
form_class = OptionForm
success_message = _("Option modifiée avec succès !")
template_name = "elections/option_update.html"
def get_success_url(self):
return (
reverse("election.admin", args=[self.object.question.election.pk])
+ f"#o_{self.object.pk}"
)
class DelOptionView(CreatorOnlyEditMixin, BackgroundUpdateView):
model = Option
success_message = _("Option supprimée !")
def get_redirect_url(self, *args, **kwargs):
return reverse("election.admin", args=[self.election.pk]) + "#q_add"
def get(self, request, *args, **kwargs):
option = self.get_object()
self.election = option.question.election
option.delete()
return super().get(request, *args, **kwargs)
# #############################################################################
# Common Views
# #############################################################################
class ElectionView(DetailView):
model = Election
template_name = "elections/election.html"
def get_next_url(self):
return self.request.path
def get_context_data(self, **kwargs):
user = self.request.user
context = super().get_context_data(**kwargs)
context["current_time"] = timezone.now()
context["can_vote"] = user.is_authenticated and user.can_vote(
context["election"]
)
return context
def get_queryset(self):
return (
super()
.get_queryset()
.filter(archived=False)
.prefetch_related("questions__options")
)
class VoteView(OpenElectionOnlyMixin, DetailView):
model = Question
template_name = "elections/vote.html"
def get_newt_url(self):
return reverse("election.view", args=[self.object.election.pk])
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
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])
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
if not self.request.user.can_vote(question.election):
raise Http404
return question
def get(self, request, *args, **kwargs):
self.object = self.get_object()
vote_form = OptionFormSet(instance=self.object)
return self.render_to_response(self.get_context_data(formset=vote_form))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
vote_form = OptionFormSet(self.request.POST, instance=self.object)
# We record the cast votes
for v in vote_form:
v.record_vote(self.request.user)
messages.success(self.request, _("Votre choix a bien été enregistré !"))
return HttpResponseRedirect(self.get_success_url())