from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django.db.models import Count, Prefetch from django.http import 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 django.views.generic.detail import SingleObjectMixin from .forms import ElectionCreateForm, OptionFormSet from .mixins import CreatorOnlyMixin from .models import Election, Option, Question # TODO: access control *everywhere* # ############################################################################# # Administration Views # ############################################################################# class ElectionCreateView(SuccessMessageMixin, CreateView): model = Election form_class = ElectionCreateForm template_name = "elections/election_create.html" success_message = _("Élection créée avec succès !") 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(SuccessMessageMixin, UpdateView): model = Election fields = ["name", "description", "start_date", "end_date"] 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]) def get_queryset(self): # On ne peut plus modifier une élection qui a déjà commencé return super().get_queryset().filter(start_date__gt=timezone.now()) class ElectionTallyView(SuccessMessageMixin, SingleObjectMixin, RedirectView): model = Election pattern_name = "election.admin" 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() messages.success(request, _("Élection dépouillée avec succès !")) return super().get(request, *args, **kwargs) class ElectionChangePublicationView( SuccessMessageMixin, SingleObjectMixin, RedirectView ): model = Election pattern_name = "election.admin" 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.results_public = not election.results_public election.save() messages.success( request, _("Élection publiée avec succès !") if election.results_public else _("Élection dépubliée avec succès !"), ) return super().get(request, *args, **kwargs) class ElectionArchiveView(SuccessMessageMixin, SingleObjectMixin, RedirectView): model = Election pattern_name = "election.admin" 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() messages.success(request, _("Élection archivée avec succès !")) return super().get(request, *args, **kwargs) class ElectionView(DetailView): model = Election template_name = "elections/election.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.object.tallied: options_qs = Option.objects.annotate(nb_votes=Count("voters")) questions = self.election.question.prefetch_related( Prefetch("options", queryset=options_qs) ) context["questions"] = questions return context def get_queryset(self): return super().get_queryset().filter(archived=False) class VoteView(SuccessMessageMixin, DetailView): model = Question template_name = "elections/vote.html" def get_queryset(self): # On ne peut voter que si l'élection n'a pas été comptée return ( super() .get_queryset() .filter( election__tallied=False, election__archived=False, election__start_date__lt=timezone.now(), election__end_date__gt=timezone.now(), ) .select_related("election") ) 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 vote a bien été enregistré !")) return HttpResponseRedirect( reverse("election.view", args=[self.object.election.pk]) + f"#q_{self.object.pk}" )