refactor election detail view

This commit is contained in:
imperosol 2025-03-16 19:11:08 +01:00
parent 57522d89c2
commit e222ac6762
5 changed files with 133 additions and 123 deletions

View File

@ -1,6 +1,7 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from core.models import User
from core.views.forms import SelectDateTime
from core.views.widgets.ajax_select import (
AutoCompleteSelect,
@ -59,22 +60,23 @@ class CandidateForm(forms.ModelForm):
class VoteForm(forms.Form):
def __init__(self, election, user, *args, **kwargs):
def __init__(self, election: Election, user: User, *args, **kwargs):
super().__init__(*args, **kwargs)
if not election.has_voted(user):
for role in election.roles.all():
cand = role.candidatures
if role.max_choice > 1:
self.fields[role.title] = LimitedCheckboxField(
cand, role.max_choice, required=False
)
else:
self.fields[role.title] = forms.ModelChoiceField(
cand,
required=False,
widget=forms.RadioSelect(),
empty_label=_("Blank vote"),
)
if election.can_vote(user):
return
for role in election.roles.all():
cand = role.candidatures
if role.max_choice > 1:
self.fields[role.title] = LimitedCheckboxField(
cand, role.max_choice, required=False
)
else:
self.fields[role.title] = forms.ModelChoiceField(
cand,
required=False,
widget=forms.RadioSelect(),
empty_label=_("Blank vote"),
)
class RoleForm(forms.ModelForm):

View File

@ -1,5 +1,6 @@
from django.db import models
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
@ -84,7 +85,7 @@ class Election(models.Model):
def has_voted(self, user):
return self.voters.filter(id=user.id).exists()
@property
@cached_property
def results(self):
results = {}
total_vote = self.voters.count()

View File

@ -31,7 +31,7 @@
<time datetime="{{ election.end_date }}">{{ election.end_date|localtime|date(DATETIME_FORMAT)}}</time>
{% trans %} at {% endtrans %}<time>{{ election.end_date|localtime|time(DATETIME_FORMAT)}}</time>
</p>
{%- if election.has_voted(user) %}
{%- if user_has_voted %}
<p class="election__elector-infos">
{%- if election.is_vote_active %}
<span>{% trans %}You already have submitted your vote.{% endtrans %}</span>
@ -45,12 +45,11 @@
<form action="{{ url('election:vote', election.id) }}" method="post" class="election__vote-form" name="vote-form" id="vote-form">
{% csrf_token %}
<table class="election_table">
{%- set election_lists = election.election_lists.all() -%}
<thead class="lists">
<tr>
<th class="column" style="width: {{ 100 / (election_lists.count() + 1) }}%">{% trans %}Blank vote{% endtrans %}</th>
<th class="column" style="width: {{ 100 / (election_lists|length + 1) }}%">{% trans %}Blank vote{% endtrans %}</th>
{%- for election_list in election_lists %}
<th class="column" style="width: {{ 100 / (election_lists.count() + 1) }}%">
<th class="column" style="width: {{ 100 / (election_lists|length + 1) }}%">
<span>{{ election_list.title }}</span>
{% if user.can_edit(election_list) and election.is_vote_editable -%}
<a href="{{ url('election:delete_list', list_id=election_list.id) }}"><i class="fa-regular fa-trash-can delete-action"></i></a>
@ -59,9 +58,7 @@
{%- endfor %}
</tr>
</thead>
{%- set role_list = election.roles.order_by('order').all() %}
{%- for role in role_list %}
{%- set count = [0] %}
{%- for role in election_roles %}
{%- set role_data = election_form.data.getlist(role.title) if role.title in election_form.data else [] %}
<tbody
@ -75,8 +72,12 @@
<div class="role_text">
<h4>{{ role.title }}</h4>
<p class="role_description" show-more="300">{{ role.description }}</p>
{%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %}
<strong>{% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %}</strong>
{%- if role.max_choice > 1 and show_vote_buttons %}
<strong>
{% trans trimmed nb_choices=role.max_choice %}
You may choose up to {{ nb_choices }} people.
{% endtrans %}
</strong>
{%- endif %}
{%- if election_form.errors[role.title] is defined %}
@ -87,36 +88,40 @@
</div>
{% if user.can_edit(role) and election.is_vote_editable -%}
<div class="role_buttons">
<a href="{{url('election:update_role', role_id=role.id)}}"><i class="fa-regular fa-pen-to-square edit-action"></i></a>
<a href="{{url('election:delete_role', role_id=role.id)}}"><i class="fa-regular fa-trash-can delete-action"></i></a>
{%- if role == role_list.last() %}
<a href="{{ url('election:update_role', role_id=role.id) }}">
<i class="fa-regular fa-pen-to-square edit-action"></i>
</a>
<a href="{{ url('election:delete_role', role_id=role.id) }}">
<i class="fa-regular fa-trash-can delete-action"></i>
</a>
{%- if loop.last -%}
<button disabled><i class="fa fa-arrow-down"></i></button>
<button disabled><i class="fa fa-caret-down"></i></button>
{%- else %}
{%- else -%}
<button type="button" onclick="window.location.replace('?role={{ role.id }}&action=bottom');"><i class="fa fa-arrow-down"></i></button>
<button type="button" onclick="window.location.replace('?role={{ role.id }}&action=down');"><i class="fa fa-caret-down"></i></button>
{%- endif %}
{% if role == role_list.first() %}
{%- endif -%}
{%- if loop.first -%}
<button disabled><i class="fa fa-caret-up"></i></button>
<button disabled><i class="fa fa-arrow-up"></i></button>
{% else %}
{%- else -%}
<button type="button" onclick="window.location.replace('?role={{ role.id }}&action=up');"><i class="fa fa-caret-up"></i></button>
<button type="button" onclick="window.location.replace('?role={{ role.id }}&action=top');"><i class="fa fa-arrow-up"></i></button>
{% endif %}
{%- endif -%}
</div>
{%- endif -%}
</td>
</tr>
<tr class="role_candidates">
<td class="list_per_role" style="width: 100%; max-width: {{ 100 / (election_lists.count() + 1) }}%">
{%- if role.max_choice == 1 and election.can_vote(user) %}
<td class="list_per_role" style="width: 100%; max-width: {{ 100 / (election_lists|length + 1) }}%">
{%- if role.max_choice == 1 and show_vote_buttons %}
<div class="radio-btn">
<input id="id_{{ role.title }}_{{ count[0] }}" type="radio" name="{{ role.title }}" value {{ '' if role_data in election_form else 'checked' }} {{ 'disabled' if election.has_voted(user) else '' }}>
<label for="id_{{ role.title }}_{{ count[0] }}">
{% set input_id = "blank_vote_" + role.id|string %}
<input id="{{ input_id }}" type="radio" name="{{ role.title }}">
<label for="{{ input_id }}">
<span>{% trans %}Choose blank vote{% endtrans %}</span>
</label>
</div>
{%- set _ = count.append(count.pop() + 1) %}
{%- endif %}
{%- if election.is_vote_finished %}
{%- set results = election_results[role.title]['blank vote'] %}
@ -126,13 +131,14 @@
{%- endif %}
</td>
{%- for election_list in election_lists %}
<td class="list_per_role" style="width: 100%; max-width: {{ 100 / (election_lists.count() + 1) }}%">
<td class="list_per_role" style="width: 100%; max-width: {{ 100 / (election_lists|length + 1) }}%">
<ul class="candidates">
{%- for candidature in election_list.candidatures.filter(role=role) %}
{%- for candidature in election_list.candidatures.select_related("user", "user__profile_pict").filter(role=role) %}
<li class="candidate">
{%- if election.can_vote(user) %}
<input id="id_{{ role.title }}_{{ count[0] }}" type="{{ 'checkbox' if role.max_choice > 1 else 'radio' }}" {{ 'checked' if candidature.id|string in role_data else '' }} {{ 'disabled' if election.has_voted(user) else '' }} name="{{ role.title }}" value="{{ candidature.id }}">
<label for="id_{{ role.title }}_{{ count[0] }}">
{%- if show_vote_buttons %}
{% set input_id = "candidature_" + candidature.id|string %}
<input id="{{ input_id }}" type="{{ 'checkbox' if role.max_choice > 1 else 'radio' }}" {{ 'checked' if candidature.id|string in role_data else '' }} {{ 'disabled' if user_has_voted else '' }} name="{{ role.title }}" value="{{ candidature.id }}">
<label for="{{ input_id }}">
{%- endif %}
<figure>
{%- if user.is_subscriber_viewable %}
@ -146,7 +152,7 @@
<h5>{{ candidature.user.first_name }} <em>{{candidature.user.nick_name or ''}} </em>{{ candidature.user.last_name }}</h5>
{%- if not election.is_vote_finished %}
<q class="candidate_program" show-more="200">
{{ candidature.program|markdown or '' }}
{{ candidature.program|markdown }}
</q>
{%- endif %}
</figcaption>
@ -159,9 +165,8 @@
{%- endif -%}
{%- endif -%}
</figure>
{%- if election.can_vote(user) %}
{%- if show_vote_buttons %}
</label>
{%- set _ = count.append(count.pop() + 1) %}
{%- endif %}
{%- if election.is_vote_finished %}
{%- set results = election_results[role.title][candidature.user.username] %}
@ -197,7 +202,7 @@
<a class="button" href="{{ url('election:delete', election_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
{%- endif %}
</section>
{%- if not election.has_voted(user) and election.can_vote(user) %}
{%- if show_vote_buttons %}
<section class="buttons">
<button class="button button_send" form="vote-form">{% trans %}Submit the vote !{% endtrans %}</button>
</section>

View File

@ -4,7 +4,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import QuerySet
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
@ -30,25 +30,21 @@ class ElectionsListView(CanViewMixin, ListView):
"""A list of all non archived elections visible."""
model = Election
queryset = model.objects.filter(archived=False)
ordering = ["-id"]
paginate_by = 10
template_name = "election/election_list.jinja"
def get_queryset(self):
return super().get_queryset().filter(archived=False).all()
class ElectionListArchivedView(CanViewMixin, ListView):
"""A list of all archived elections visible."""
model = Election
queryset = model.objects.filter(archived=True)
ordering = ["-id"]
paginate_by = 10
template_name = "election/election_list.jinja"
def get_queryset(self):
return super().get_queryset().filter(archived=True).all()
class ElectionDetailView(CanViewMixin, DetailView):
"""Details an election responsability by responsability."""
@ -57,32 +53,42 @@ class ElectionDetailView(CanViewMixin, DetailView):
template_name = "election/election_detail.jinja"
pk_url_kwarg = "election_id"
@staticmethod
def _reorder_votes(action: str, role: int):
role = Role.objects.filter(id=role).first()
if not role:
return
if action == "up":
role.up()
elif action == "down":
role.down()
elif action == "bottom":
role.bottom()
elif action == "top":
role.top()
def get(self, request, *arg, **kwargs):
response = super().get(request, *arg, **kwargs)
election: Election = self.get_object()
if request.user.can_edit(election) and election.is_vote_editable:
if election.is_vote_editable and request.user.can_edit(election):
action = request.GET.get("action", None)
role = request.GET.get("role", None)
if action and role and Role.objects.filter(id=role).exists():
if action == "up":
Role.objects.get(id=role).up()
elif action == "down":
Role.objects.get(id=role).down()
elif action == "bottom":
Role.objects.get(id=role).bottom()
elif action == "top":
Role.objects.get(id=role).top()
return redirect(
reverse("election:detail", kwargs={"election_id": election.id})
)
return response
if action and role and role.isdigit():
self._reorder_votes(action, int(role))
return super().get(request, *arg, **kwargs)
def get_context_data(self, **kwargs):
"""Add additionnal data to the template."""
kwargs = super().get_context_data(**kwargs)
kwargs["election_form"] = VoteForm(self.object, self.request.user)
kwargs["election_results"] = self.object.results
return kwargs
user: User = self.request.user
return super().get_context_data(**kwargs) | {
"election_form": VoteForm(self.object, user),
"show_vote_buttons": self.object.can_vote(user),
"user_has_voted": self.object.has_voted(user),
"election_results": (
self.object.results if self.object.is_vote_finished else None
),
"election_lists": list(self.object.election_lists.all()),
"election_roles": list(self.object.roles.order_by("order")),
}
# Form view
@ -363,18 +369,12 @@ class RoleUpdateView(CanEditMixin, UpdateView):
# Delete Views
class ElectionDeleteView(DeleteView):
class ElectionDeleteView(PermissionRequiredMixin, DeleteView):
model = Election
template_name = "core/delete_confirm.jinja"
pk_url_kwarg = "election_id"
def dispatch(self, request, *args, **kwargs):
if request.user.is_root:
return super().dispatch(request, *args, **kwargs)
raise PermissionDenied
def get_success_url(self, **kwargs):
return reverse_lazy("election:list")
permission_required = "election.delete_election"
success_url = reverse_lazy("election:list")
class CandidatureDeleteView(CanEditMixin, DeleteView):
@ -390,7 +390,7 @@ class CandidatureDeleteView(CanEditMixin, DeleteView):
return super().dispatch(request, *arg, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.election.id})
return reverse("election:detail", kwargs={"election_id": self.election.id})
class RoleDeleteView(CanEditMixin, DeleteView):
@ -406,7 +406,7 @@ class RoleDeleteView(CanEditMixin, DeleteView):
return super().dispatch(request, *arg, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.election.id})
return reverse("election:detail", kwargs={"election_id": self.election.id})
class ElectionListDeleteView(CanEditMixin, DeleteView):
@ -422,4 +422,4 @@ class ElectionListDeleteView(CanEditMixin, DeleteView):
return super().dispatch(request, *args, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.election.id})
return reverse("election:detail", kwargs={"election_id": self.election.id})

View File

@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-25 16:38+0100\n"
"POT-Creation-Date: 2025-03-16 19:08+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@ -841,7 +841,7 @@ msgstr "vous devez spécifier au moins un utilisateur ou une adresse email"
msgid "Begin date"
msgstr "Date de début"
#: club/forms.py com/forms.py counter/forms.py election/views.py
#: club/forms.py com/forms.py counter/forms.py election/forms.py
#: subscription/forms.py
msgid "End date"
msgstr "Date de fin"
@ -1263,7 +1263,7 @@ msgstr "Propriétés"
msgid "Format: 16:9 | Resolution: 1920x1080"
msgstr "Format : 16:9 | Résolution : 1920x1080"
#: com/forms.py election/views.py subscription/forms.py
#: com/forms.py election/forms.py subscription/forms.py
msgid "Start date"
msgstr "Date de début"
@ -2837,6 +2837,7 @@ msgid "Users"
msgstr "Utilisateurs"
#: core/templates/core/search.jinja core/views/user.py
#: counter/templates/counter/product_list.jinja
msgid "Clubs"
msgstr "Clubs"
@ -3182,7 +3183,7 @@ msgid "Bans"
msgstr "Bans"
#: core/templates/core/user_tools.jinja counter/forms.py
#: counter/views/mixins.py
#: counter/templates/counter/product_list.jinja counter/views/mixins.py
msgid "Counters"
msgstr "Comptoirs"
@ -4359,6 +4360,30 @@ msgstr "Le paiement a échoué"
msgid "Return to eboutic"
msgstr "Retourner à l'eboutic"
#: election/forms.py
msgid "You have selected too much candidates."
msgstr "Vous avez sélectionné trop de candidats."
#: election/forms.py
msgid "User to candidate"
msgstr "Utilisateur se présentant"
#: election/forms.py election/templates/election/election_detail.jinja
msgid "Blank vote"
msgstr "Vote blanc"
#: election/forms.py
msgid "This role already exists for this election"
msgstr "Ce rôle existe déjà pour cette élection"
#: election/forms.py
msgid "Start candidature"
msgstr "Début des candidatures"
#: election/forms.py
msgid "End candidature"
msgstr "Fin des candidatures"
#: election/models.py
msgid "start candidature"
msgstr "début des candidatures"
@ -4383,6 +4408,10 @@ msgstr "groupe de vote"
msgid "candidature groups"
msgstr "groupe de candidature"
#: election/models.py
msgid "voters"
msgstr "électeurs"
#: election/models.py
msgid "election"
msgstr "élection"
@ -4438,17 +4467,10 @@ msgstr "Vous avez déjà soumis votre vote."
msgid "You have voted in this election."
msgstr "Vous avez déjà voté pour cette élection."
#: election/templates/election/election_detail.jinja election/views.py
msgid "Blank vote"
msgstr "Vote blanc"
#: election/templates/election/election_detail.jinja
msgid "You may choose up to"
msgstr "Vous pouvez choisir jusqu'à"
#: election/templates/election/election_detail.jinja
msgid "people."
msgstr "personne(s)"
#, python-format
msgid "You may choose up to %(nb_choices)s people."
msgstr "Vous pouvez choisir jusqu'à %(nb_choices)s personnes."
#: election/templates/election/election_detail.jinja
msgid "Choose blank vote"
@ -4490,26 +4512,6 @@ msgstr "au"
msgid "Polls open from"
msgstr "Votes ouverts du"
#: election/views.py
msgid "You have selected too much candidates."
msgstr "Vous avez sélectionné trop de candidats."
#: election/views.py
msgid "User to candidate"
msgstr "Utilisateur se présentant"
#: election/views.py
msgid "This role already exists for this election"
msgstr "Ce rôle existe déjà pour cette élection"
#: election/views.py
msgid "Start candidature"
msgstr "Début des candidatures"
#: election/views.py
msgid "End candidature"
msgstr "Fin des candidatures"
#: forum/models.py
msgid "is a category"
msgstr "est une catégorie"
@ -5219,15 +5221,15 @@ msgstr "SAS"
msgid "Albums"
msgstr "Albums"
#: sas/templates/sas/album.jinja
msgid "Download album"
msgstr "Télécharger l'album"
#: sas/templates/sas/album.jinja sas/templates/sas/macros.jinja
#: sas/templates/sas/user_pictures.jinja
msgid "To be moderated"
msgstr "A modérer"
#: sas/templates/sas/album.jinja
msgid "Download album"
msgstr "Télécharger l'album"
#: sas/templates/sas/album.jinja
msgid "Upload"
msgstr "Envoyer"