From 6c8743a403d98a6bd58d3e42dcd2fea48e02c52d Mon Sep 17 00:00:00 2001 From: imperosol Date: Fri, 14 Mar 2025 19:19:20 +0100 Subject: [PATCH] refactor CandidatureForm --- election/forms.py | 25 +++----- ...didature_program_alter_candidature_user.py | 30 ++++++++++ election/models.py | 42 ++++--------- election/views.py | 60 +++++-------------- 4 files changed, 66 insertions(+), 91 deletions(-) create mode 100644 election/migrations/0005_alter_candidature_program_alter_candidature_user.py diff --git a/election/forms.py b/election/forms.py index 5032e859..0e901c4c 100644 --- a/election/forms.py +++ b/election/forms.py @@ -1,12 +1,6 @@ from django import forms -from django.db import transaction -from django.db.models import QuerySet -from django.shortcuts import get_object_or_404 -from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import FormView -from core.auth.mixins import CanCreateMixin from core.views.forms import SelectDateTime from core.views.widgets.ajax_select import ( AutoCompleteSelect, @@ -14,7 +8,7 @@ from core.views.widgets.ajax_select import ( AutoCompleteSelectUser, ) from core.views.widgets.markdown import MarkdownInput -from election.models import Candidature, Election, ElectionList, Role, Vote +from election.models import Candidature, Election, ElectionList, Role class LimitedCheckboxField(forms.ModelMultipleChoiceField): @@ -39,6 +33,8 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField): class CandidateForm(forms.ModelForm): """Form to candidate.""" + required_css_class = "required" + class Meta: model = Candidature fields = ["user", "role", "program", "election_list"] @@ -52,17 +48,12 @@ class CandidateForm(forms.ModelForm): "election_list": AutoCompleteSelect, } - def __init__(self, *args, **kwargs): - election_id = kwargs.pop("election_id", None) - can_edit = kwargs.pop("can_edit", False) + def __init__( + self, *args, election: Election | None, can_edit: bool = False, **kwargs + ): super().__init__(*args, **kwargs) - if election_id: - self.fields["role"].queryset = Role.objects.filter( - election__id=election_id - ).all() - self.fields["election_list"].queryset = ElectionList.objects.filter( - election__id=election_id - ).all() + self.fields["role"].queryset = election.roles.select_related("election") + self.fields["election_list"].queryset = election.election_lists.all() if not can_edit: self.fields["user"].widget = forms.HiddenInput() diff --git a/election/migrations/0005_alter_candidature_program_alter_candidature_user.py b/election/migrations/0005_alter_candidature_program_alter_candidature_user.py new file mode 100644 index 00000000..92930b2d --- /dev/null +++ b/election/migrations/0005_alter_candidature_program_alter_candidature_user.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.20 on 2025-03-14 18:18 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("election", "0004_auto_20191006_0049"), + ] + + operations = [ + migrations.AlterField( + model_name="candidature", + name="program", + field=models.TextField(blank=True, default="", verbose_name="description"), + ), + migrations.AlterField( + model_name="candidature", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="candidates", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), + ] diff --git a/election/models.py b/election/models.py index dc6ab474..a3abd019 100644 --- a/election/models.py +++ b/election/models.py @@ -22,21 +22,18 @@ class Election(models.Model): verbose_name=_("edit groups"), blank=True, ) - view_groups = models.ManyToManyField( Group, related_name="viewable_elections", verbose_name=_("view groups"), blank=True, ) - vote_groups = models.ManyToManyField( Group, related_name="votable_elections", verbose_name=_("vote groups"), blank=True, ) - candidature_groups = models.ManyToManyField( Group, related_name="candidate_elections", @@ -45,7 +42,7 @@ class Election(models.Model): ) voters = models.ManyToManyField( - User, verbose_name=("voters"), related_name="voted_elections" + User, verbose_name=_("voters"), related_name="voted_elections" ) archived = models.BooleanField(_("archived"), default=False) @@ -55,20 +52,20 @@ class Election(models.Model): @property def is_vote_active(self): now = timezone.now() - return bool(now <= self.end_date and now >= self.start_date) + return self.start_date <= now <= self.end_date @property def is_vote_finished(self): - return bool(timezone.now() > self.end_date) + return timezone.now() > self.end_date @property def is_candidature_active(self): now = timezone.now() - return bool(now <= self.end_candidature and now >= self.start_candidature) + return self.start_candidature <= now <= self.end_candidature @property def is_vote_editable(self): - return bool(timezone.now() <= self.end_candidature) + return timezone.now() <= self.end_candidature def can_candidate(self, user): for group_id in self.candidature_groups.values_list("pk", flat=True): @@ -95,12 +92,6 @@ class Election(models.Model): results[role.title] = role.results(total_vote) return results - def delete(self, *args, **kwargs): - self.election_lists.all().delete() - super().delete(*args, **kwargs) - - # Permissions - class Role(OrderedModel): """This class allows to create a new role avaliable for a candidature.""" @@ -115,6 +106,9 @@ class Role(OrderedModel): description = models.TextField(_("description"), null=True, blank=True) max_choice = models.IntegerField(_("max choice"), default=1) + def __str__(self): + return f"{self.title} - {self.election.title}" + def results(self, total_vote): results = {} total_vote *= self.max_choice @@ -142,9 +136,6 @@ class Role(OrderedModel): def edit_groups(self): return self.election.edit_groups - def __str__(self): - return ("%s : %s") % (self.election.title, self.title) - class ElectionList(models.Model): """To allow per list vote.""" @@ -163,11 +154,6 @@ class ElectionList(models.Model): def can_be_edited_by(self, user): return user.can_edit(self.election) - def delete(self, *args, **kwargs): - for candidature in self.candidatures.all(): - candidature.delete() - super().delete(*args, **kwargs) - class Candidature(models.Model): """This class is a component of responsability.""" @@ -182,10 +168,9 @@ class Candidature(models.Model): User, verbose_name=_("user"), related_name="candidates", - blank=True, on_delete=models.CASCADE, ) - program = models.TextField(_("description"), null=True, blank=True) + program = models.TextField(_("description"), default="", blank=True) election_list = models.ForeignKey( ElectionList, related_name="candidatures", @@ -196,13 +181,10 @@ class Candidature(models.Model): def __str__(self): return f"{self.role.title} : {self.user.username}" - def delete(self): - for vote in self.votes.all(): - vote.delete() - super().delete() - def can_be_edited_by(self, user): - return (user == self.user) or user.can_edit(self.role.election) + return ( + (user == self.user) or user.can_edit(self.role.election) + ) and self.role.election.is_vote_editable class Vote(models.Model): diff --git a/election/views.py b/election/views.py index ed0570e5..84587a9e 100644 --- a/election/views.py +++ b/election/views.py @@ -155,26 +155,22 @@ class CandidatureCreateView(LoginRequiredMixin, CreateView): def dispatch(self, request, *arg, **kwargs): self.election = get_object_or_404(Election, pk=kwargs["election_id"]) + self.can_edit = self.request.user.can_edit(self.election) return super().dispatch(request, *arg, **kwargs) def get_initial(self): - init = {} - self.can_edit = self.request.user.can_edit(self.election) - init["user"] = self.request.user.id - return init + return {"user": self.request.user.id} def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - kwargs["election_id"] = self.election.id - kwargs["can_edit"] = self.can_edit - return kwargs + return super().get_form_kwargs() | { + "election": self.election, + "can_edit": self.can_edit, + } - def form_valid(self, form): + def form_valid(self, form: CandidateForm): """Verify that the selected user is in candidate group.""" obj = form.instance obj.election = self.election - if not hasattr(obj, "user"): - obj.user = self.request.user if (obj.election.can_candidate(obj.user)) and ( obj.user == self.request.user or self.can_edit ): @@ -182,9 +178,7 @@ class CandidatureCreateView(LoginRequiredMixin, CreateView): raise PermissionDenied def get_context_data(self, **kwargs): - kwargs = super().get_context_data(**kwargs) - kwargs["election"] = self.election - return kwargs + return super().get_context_data(**kwargs) | {"election": self.election} def get_success_url(self, **kwargs): return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) @@ -302,45 +296,23 @@ class ElectionUpdateView(CanEditMixin, UpdateView): return reverse_lazy("election:detail", kwargs={"election_id": self.object.id}) -class CandidatureUpdateView(CanEditMixin, UpdateView): +class CandidatureUpdateView(LoginRequiredMixin, CanEditMixin, UpdateView): model = Candidature form_class = CandidateForm template_name = "core/edit.jinja" pk_url_kwarg = "candidature_id" - def dispatch(self, request, *arg, **kwargs): - self.object = self.get_object() - if not self.object.role.election.is_vote_editable: - raise PermissionDenied - return super().dispatch(request, *arg, **kwargs) - - def remove_fields(self): - self.form.fields.pop("role", None) - - def get(self, request, *args, **kwargs): - self.form = self.get_form() - self.remove_fields() - return self.render_to_response(self.get_context_data(form=self.form)) - - def post(self, request, *args, **kwargs): - self.form = self.get_form() - self.remove_fields() - if ( - request.user.is_authenticated - and request.user.can_edit(self.object) - and self.form.is_valid() - ): - return super().form_valid(self.form) - return self.form_invalid(self.form) + def get_form(self, *args, **kwargs): + form = super().get_form(*args, **kwargs) + form.fields.pop("role", None) + return form def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - kwargs["election_id"] = self.object.role.election.id - return kwargs + return super().get_form_kwargs() | {"election": self.object.role.election} def get_success_url(self, **kwargs): - return reverse_lazy( - "election:detail", kwargs={"election_id": self.object.role.election.id} + return reverse( + "election:detail", kwargs={"election_id": self.object.role.election_id} )