refactor CandidatureForm

This commit is contained in:
imperosol 2025-03-14 19:19:20 +01:00
parent b076b854fe
commit 6c8743a403
4 changed files with 66 additions and 91 deletions

View File

@ -1,12 +1,6 @@
from django import forms 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.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.forms import SelectDateTime
from core.views.widgets.ajax_select import ( from core.views.widgets.ajax_select import (
AutoCompleteSelect, AutoCompleteSelect,
@ -14,7 +8,7 @@ from core.views.widgets.ajax_select import (
AutoCompleteSelectUser, AutoCompleteSelectUser,
) )
from core.views.widgets.markdown import MarkdownInput 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): class LimitedCheckboxField(forms.ModelMultipleChoiceField):
@ -39,6 +33,8 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
class CandidateForm(forms.ModelForm): class CandidateForm(forms.ModelForm):
"""Form to candidate.""" """Form to candidate."""
required_css_class = "required"
class Meta: class Meta:
model = Candidature model = Candidature
fields = ["user", "role", "program", "election_list"] fields = ["user", "role", "program", "election_list"]
@ -52,17 +48,12 @@ class CandidateForm(forms.ModelForm):
"election_list": AutoCompleteSelect, "election_list": AutoCompleteSelect,
} }
def __init__(self, *args, **kwargs): def __init__(
election_id = kwargs.pop("election_id", None) self, *args, election: Election | None, can_edit: bool = False, **kwargs
can_edit = kwargs.pop("can_edit", False) ):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if election_id: self.fields["role"].queryset = election.roles.select_related("election")
self.fields["role"].queryset = Role.objects.filter( self.fields["election_list"].queryset = election.election_lists.all()
election__id=election_id
).all()
self.fields["election_list"].queryset = ElectionList.objects.filter(
election__id=election_id
).all()
if not can_edit: if not can_edit:
self.fields["user"].widget = forms.HiddenInput() self.fields["user"].widget = forms.HiddenInput()

View File

@ -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",
),
),
]

View File

@ -22,21 +22,18 @@ class Election(models.Model):
verbose_name=_("edit groups"), verbose_name=_("edit groups"),
blank=True, blank=True,
) )
view_groups = models.ManyToManyField( view_groups = models.ManyToManyField(
Group, Group,
related_name="viewable_elections", related_name="viewable_elections",
verbose_name=_("view groups"), verbose_name=_("view groups"),
blank=True, blank=True,
) )
vote_groups = models.ManyToManyField( vote_groups = models.ManyToManyField(
Group, Group,
related_name="votable_elections", related_name="votable_elections",
verbose_name=_("vote groups"), verbose_name=_("vote groups"),
blank=True, blank=True,
) )
candidature_groups = models.ManyToManyField( candidature_groups = models.ManyToManyField(
Group, Group,
related_name="candidate_elections", related_name="candidate_elections",
@ -45,7 +42,7 @@ class Election(models.Model):
) )
voters = models.ManyToManyField( 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) archived = models.BooleanField(_("archived"), default=False)
@ -55,20 +52,20 @@ class Election(models.Model):
@property @property
def is_vote_active(self): def is_vote_active(self):
now = timezone.now() now = timezone.now()
return bool(now <= self.end_date and now >= self.start_date) return self.start_date <= now <= self.end_date
@property @property
def is_vote_finished(self): def is_vote_finished(self):
return bool(timezone.now() > self.end_date) return timezone.now() > self.end_date
@property @property
def is_candidature_active(self): def is_candidature_active(self):
now = timezone.now() now = timezone.now()
return bool(now <= self.end_candidature and now >= self.start_candidature) return self.start_candidature <= now <= self.end_candidature
@property @property
def is_vote_editable(self): def is_vote_editable(self):
return bool(timezone.now() <= self.end_candidature) return timezone.now() <= self.end_candidature
def can_candidate(self, user): def can_candidate(self, user):
for group_id in self.candidature_groups.values_list("pk", flat=True): 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) results[role.title] = role.results(total_vote)
return results return results
def delete(self, *args, **kwargs):
self.election_lists.all().delete()
super().delete(*args, **kwargs)
# Permissions
class Role(OrderedModel): class Role(OrderedModel):
"""This class allows to create a new role avaliable for a candidature.""" """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) description = models.TextField(_("description"), null=True, blank=True)
max_choice = models.IntegerField(_("max choice"), default=1) max_choice = models.IntegerField(_("max choice"), default=1)
def __str__(self):
return f"{self.title} - {self.election.title}"
def results(self, total_vote): def results(self, total_vote):
results = {} results = {}
total_vote *= self.max_choice total_vote *= self.max_choice
@ -142,9 +136,6 @@ class Role(OrderedModel):
def edit_groups(self): def edit_groups(self):
return self.election.edit_groups return self.election.edit_groups
def __str__(self):
return ("%s : %s") % (self.election.title, self.title)
class ElectionList(models.Model): class ElectionList(models.Model):
"""To allow per list vote.""" """To allow per list vote."""
@ -163,11 +154,6 @@ class ElectionList(models.Model):
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
return user.can_edit(self.election) 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): class Candidature(models.Model):
"""This class is a component of responsability.""" """This class is a component of responsability."""
@ -182,10 +168,9 @@ class Candidature(models.Model):
User, User,
verbose_name=_("user"), verbose_name=_("user"),
related_name="candidates", related_name="candidates",
blank=True,
on_delete=models.CASCADE, on_delete=models.CASCADE,
) )
program = models.TextField(_("description"), null=True, blank=True) program = models.TextField(_("description"), default="", blank=True)
election_list = models.ForeignKey( election_list = models.ForeignKey(
ElectionList, ElectionList,
related_name="candidatures", related_name="candidatures",
@ -196,13 +181,10 @@ class Candidature(models.Model):
def __str__(self): def __str__(self):
return f"{self.role.title} : {self.user.username}" 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): 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): class Vote(models.Model):

View File

@ -155,26 +155,22 @@ class CandidatureCreateView(LoginRequiredMixin, CreateView):
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
self.election = get_object_or_404(Election, pk=kwargs["election_id"]) 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) return super().dispatch(request, *arg, **kwargs)
def get_initial(self): def get_initial(self):
init = {} return {"user": self.request.user.id}
self.can_edit = self.request.user.can_edit(self.election)
init["user"] = self.request.user.id
return init
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() return super().get_form_kwargs() | {
kwargs["election_id"] = self.election.id "election": self.election,
kwargs["can_edit"] = self.can_edit "can_edit": self.can_edit,
return kwargs }
def form_valid(self, form): def form_valid(self, form: CandidateForm):
"""Verify that the selected user is in candidate group.""" """Verify that the selected user is in candidate group."""
obj = form.instance obj = form.instance
obj.election = self.election obj.election = self.election
if not hasattr(obj, "user"):
obj.user = self.request.user
if (obj.election.can_candidate(obj.user)) and ( if (obj.election.can_candidate(obj.user)) and (
obj.user == self.request.user or self.can_edit obj.user == self.request.user or self.can_edit
): ):
@ -182,9 +178,7 @@ class CandidatureCreateView(LoginRequiredMixin, CreateView):
raise PermissionDenied raise PermissionDenied
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) return super().get_context_data(**kwargs) | {"election": self.election}
kwargs["election"] = self.election
return kwargs
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) 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}) return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
class CandidatureUpdateView(CanEditMixin, UpdateView): class CandidatureUpdateView(LoginRequiredMixin, CanEditMixin, UpdateView):
model = Candidature model = Candidature
form_class = CandidateForm form_class = CandidateForm
template_name = "core/edit.jinja" template_name = "core/edit.jinja"
pk_url_kwarg = "candidature_id" pk_url_kwarg = "candidature_id"
def dispatch(self, request, *arg, **kwargs): def get_form(self, *args, **kwargs):
self.object = self.get_object() form = super().get_form(*args, **kwargs)
if not self.object.role.election.is_vote_editable: form.fields.pop("role", None)
raise PermissionDenied return form
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_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() return super().get_form_kwargs() | {"election": self.object.role.election}
kwargs["election_id"] = self.object.role.election.id
return kwargs
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
return reverse_lazy( return reverse(
"election:detail", kwargs={"election_id": self.object.role.election.id} "election:detail", kwargs={"election_id": self.object.role.election_id}
) )