Sith/election/models.py
2025-03-16 21:53:11 +01:00

205 lines
6.2 KiB
Python

from django.db import models
from django.db.models import Count
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from ordered_model.models import OrderedModel
from core.models import Group, User
class Election(models.Model):
"""This class allows to create a new election."""
title = models.CharField(_("title"), max_length=255)
description = models.TextField(_("description"), null=True, blank=True)
start_candidature = models.DateTimeField(_("start candidature"), blank=False)
end_candidature = models.DateTimeField(_("end candidature"), blank=False)
start_date = models.DateTimeField(_("start date"), blank=False)
end_date = models.DateTimeField(_("end date"), blank=False)
edit_groups = models.ManyToManyField(
Group,
related_name="editable_elections",
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",
verbose_name=_("candidature groups"),
blank=True,
)
voters = models.ManyToManyField(
User, verbose_name=_("voters"), related_name="voted_elections"
)
archived = models.BooleanField(_("archived"), default=False)
def __str__(self):
return self.title
@property
def is_vote_active(self):
now = timezone.now()
return self.start_date <= now <= self.end_date
@property
def is_vote_finished(self):
return timezone.now() > self.end_date
@property
def is_candidature_active(self):
now = timezone.now()
return self.start_candidature <= now <= self.end_candidature
@property
def is_vote_editable(self):
return timezone.now() <= self.end_candidature
def can_candidate(self, user):
for group_id in self.candidature_groups.values_list("pk", flat=True):
if user.is_in_group(pk=group_id):
return True
return False
def can_vote(self, user):
if not self.is_vote_active or self.has_voted(user):
return False
for group_id in self.vote_groups.values_list("pk", flat=True):
if user.is_in_group(pk=group_id):
return True
return False
def has_voted(self, user):
return self.voters.filter(id=user.id).exists()
@cached_property
def results(self):
results = {}
total_vote = self.voters.count()
for role in self.roles.all():
results[role.title] = role.results(total_vote)
return results
class Role(OrderedModel):
"""This class allows to create a new role avaliable for a candidature."""
election = models.ForeignKey(
Election,
related_name="roles",
verbose_name=_("election"),
on_delete=models.CASCADE,
)
title = models.CharField(_("title"), max_length=255)
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: int) -> dict[str, dict[str, int | float]]:
if total_vote == 0:
candidates = self.candidatures.values_list("user__username")
return {
key: {"vote": 0, "percent": 0} for key in ["blank_votes", *candidates]
}
total_vote *= self.max_choice
results = {"total vote": total_vote}
non_blank = 0
candidatures = self.candidatures.annotate(nb_votes=Count("votes")).values(
"nb_votes", "user__username"
)
for candidature in candidatures:
non_blank += candidature["nb_votes"]
results[candidature["user__username"]] = {
"vote": candidature["nb_votes"],
"percent": candidature["nb_votes"] * 100 / total_vote,
}
results["blank vote"] = {
"vote": total_vote - non_blank,
"percent": (total_vote - non_blank) * 100 / total_vote,
}
return results
@property
def edit_groups(self):
return self.election.edit_groups
class ElectionList(models.Model):
"""To allow per list vote."""
title = models.CharField(_("title"), max_length=255)
election = models.ForeignKey(
Election,
related_name="election_lists",
verbose_name=_("election"),
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
def can_be_edited_by(self, user):
return user.can_edit(self.election)
class Candidature(models.Model):
"""This class is a component of responsability."""
role = models.ForeignKey(
Role,
related_name="candidatures",
verbose_name=_("role"),
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
verbose_name=_("user"),
related_name="candidates",
on_delete=models.CASCADE,
)
program = models.TextField(_("description"), default="", blank=True)
election_list = models.ForeignKey(
ElectionList,
related_name="candidatures",
verbose_name=_("election list"),
on_delete=models.CASCADE,
)
def __str__(self):
return f"{self.role.title} : {self.user.username}"
def can_be_edited_by(self, user):
return (
(user == self.user) or user.can_edit(self.role.election)
) and self.role.election.is_vote_editable
class Vote(models.Model):
"""This class allows to vote for candidates."""
role = models.ForeignKey(
Role, related_name="votes", verbose_name=_("role"), on_delete=models.CASCADE
)
candidature = models.ManyToManyField(
Candidature, related_name="votes", verbose_name=_("candidature")
)
def __str__(self):
return "Vote"