fix: bad value for blank vote and better flow for invalid form

* Add an error message when looking at a public election without being logged in
* Add correct value for blank vote on single vote field
* Redirect to view with an error message if an invalid form has been submitted
This commit is contained in:
2026-01-14 11:30:04 +01:00
parent 8c6f7c82c9
commit 2744282fd8
4 changed files with 55 additions and 29 deletions

View File

@@ -60,8 +60,6 @@ class CandidateForm(forms.ModelForm):
class VoteForm(forms.Form): class VoteForm(forms.Form):
def __init__(self, election: Election, user: User, *args, **kwargs): def __init__(self, election: Election, user: User, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if not election.can_vote(user):
return
for role in election.roles.all(): for role in election.roles.all():
cand = role.candidatures cand = role.candidatures
if role.max_choice > 1: if role.max_choice > 1:
@@ -74,6 +72,7 @@ class VoteForm(forms.Form):
required=False, required=False,
widget=forms.RadioSelect(), widget=forms.RadioSelect(),
empty_label=_("Blank vote"), empty_label=_("Blank vote"),
blank=True,
) )

View File

@@ -14,6 +14,11 @@
{% block content %} {% block content %}
<h3 class="election__title">{{ election.title }}</h3> <h3 class="election__title">{{ election.title }}</h3>
{% if user.is_anonymous %}
<div class="alert alert-red">
{% trans %}You are not logged in, candidate pictures won't display for privacy reasons.{% endtrans %}
</div>
{% endif %}
<p class="election__description">{{ election.description }}</p> <p class="election__description">{{ election.description }}</p>
<hr> <hr>
<section class="election_details"> <section class="election_details">
@@ -117,7 +122,7 @@
{%- if role.max_choice == 1 and show_vote_buttons %} {%- if role.max_choice == 1 and show_vote_buttons %}
<div class="radio-btn"> <div class="radio-btn">
{% set input_id = "blank_vote_" + role.id|string %} {% set input_id = "blank_vote_" + role.id|string %}
<input id="{{ input_id }}" type="radio" name="{{ role.title }}"> <input id="{{ input_id }}" type="radio" name="{{ role.title }}" value="" checked>
<label for="{{ input_id }}"> <label for="{{ input_id }}">
<span>{% trans %}Choose blank vote{% endtrans %}</span> <span>{% trans %}Choose blank vote{% endtrans %}</span>
</label> </label>
@@ -185,26 +190,28 @@
</table> </table>
</form> </form>
</section> </section>
<section class="buttons"> {% if not user.is_anonymous %}
{%- if (election.can_candidate(user) and election.is_candidature_active) or (user.can_edit(election) and election.is_vote_editable) %}
<a class="button" href="{{ url('election:candidate', election_id=object.id) }}">{% trans %}Candidate{% endtrans %}</a>
{%- endif %}
{%- if election.is_vote_editable %}
<a class="button" href="{{ url('election:create_list', election_id=object.id) }}">{% trans %}Add a new list{% endtrans %}</a>
{%- endif %}
{%- if user.can_edit(election) %}
{% if election.is_vote_editable %}
<a class="button" href="{{ url('election:create_role', election_id=object.id) }}">{% trans %}Add a new role{% endtrans %}</a>
{% endif %}
<a class="button" href="{{ url('election:update', election_id=object.id) }}">{% trans %}Edit{% endtrans %}</a>
{%- endif %}
{%- if user.is_root %}
<a class="button" href="{{ url('election:delete', election_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
{%- endif %}
</section>
{%- if show_vote_buttons %}
<section class="buttons"> <section class="buttons">
<button class="button button_send" form="vote-form">{% trans %}Submit the vote !{% endtrans %}</button> {%- if (election.can_candidate(user) and election.is_candidature_active) or (user.can_edit(election) and election.is_vote_editable) %}
<a class="button" href="{{ url('election:candidate', election_id=object.id) }}">{% trans %}Candidate{% endtrans %}</a>
{%- endif %}
{%- if election.is_vote_editable %}
<a class="button" href="{{ url('election:create_list', election_id=object.id) }}">{% trans %}Add a new list{% endtrans %}</a>
{%- endif %}
{%- if user.can_edit(election) %}
{% if election.is_vote_editable %}
<a class="button" href="{{ url('election:create_role', election_id=object.id) }}">{% trans %}Add a new role{% endtrans %}</a>
{% endif %}
<a class="button" href="{{ url('election:update', election_id=object.id) }}">{% trans %}Edit{% endtrans %}</a>
{%- endif %}
{%- if user.is_root %}
<a class="button" href="{{ url('election:delete', election_id=object.id) }}">{% trans %}Delete{% endtrans %}</a>
{%- endif %}
</section> </section>
{%- endif %} {%- if show_vote_buttons %}
<section class="buttons">
<button class="button button_send" form="vote-form">{% trans %}Submit the vote !{% endtrans %}</button>
</section>
{%- endif %}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
from cryptography.utils import cached_property from cryptography.utils import cached_property
from django.conf import settings from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import ( from django.contrib.auth.mixins import (
LoginRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
@@ -10,8 +11,9 @@ from django.contrib.auth.mixins import (
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet from django.db.models import QuerySet
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
@@ -53,7 +55,7 @@ class ElectionListArchivedView(CanViewMixin, ListView):
class ElectionDetailView(CanViewMixin, DetailView): class ElectionDetailView(CanViewMixin, DetailView):
"""Details an election responsability by responsability.""" """Details an election responsibility by responsibility."""
model = Election model = Election
template_name = "election/election_detail.jinja" template_name = "election/election_detail.jinja"
@@ -83,7 +85,7 @@ class ElectionDetailView(CanViewMixin, DetailView):
return super().get(request, *arg, **kwargs) return super().get(request, *arg, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add additionnal data to the template.""" """Add additional data to the template."""
user: User = self.request.user user: User = self.request.user
return super().get_context_data(**kwargs) | { return super().get_context_data(**kwargs) | {
"election_form": VoteForm(self.object, user), "election_form": VoteForm(self.object, user),
@@ -101,7 +103,7 @@ class ElectionDetailView(CanViewMixin, DetailView):
class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView): class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView):
"""Alows users to vote.""" """Allows users to vote."""
form_class = VoteForm form_class = VoteForm
template_name = "election/election_detail.jinja" template_name = "election/election_detail.jinja"
@@ -111,6 +113,9 @@ class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView):
return get_object_or_404(Election, pk=self.kwargs["election_id"]) return get_object_or_404(Election, pk=self.kwargs["election_id"])
def test_func(self): def test_func(self):
if not self.election.can_vote(self.request.user):
return False
groups = set(self.election.vote_groups.values_list("id", flat=True)) groups = set(self.election.vote_groups.values_list("id", flat=True))
if ( if (
settings.SITH_GROUP_SUBSCRIBERS_ID in groups settings.SITH_GROUP_SUBSCRIBERS_ID in groups
@@ -150,11 +155,17 @@ class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView):
self.vote(data) self.vote(data)
return super().form_valid(form) return super().form_valid(form)
def form_invalid(self, form):
messages.error(self.request, _("Form is invalid"))
return redirect(
reverse("election:detail", kwargs={"election_id": self.election.id}),
)
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})
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add additionnal data to the template.""" """Add additional data to the template."""
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs["object"] = self.election kwargs["object"] = self.election
kwargs["election"] = self.election kwargs["election"] = self.election

View File

@@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-19 23:10+0100\n" "POT-Creation-Date: 2026-01-14 11:34+0100\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -4108,6 +4108,11 @@ msgstr "Candidater"
msgid "Candidature are closed for this election" msgid "Candidature are closed for this election"
msgstr "Les candidatures sont fermées pour cette élection" msgstr "Les candidatures sont fermées pour cette élection"
#: election/templates/election/election_detail.jinja
msgid ""
"You are not logged in, candidate pictures won't display for privacy reasons."
msgstr "Vous n'êtes pas connecté, les photos des candidats ne s'afficheront pas pour des raisons de respect de la vie privée."
#: election/templates/election/election_detail.jinja #: election/templates/election/election_detail.jinja
msgid "Polls close " msgid "Polls close "
msgstr "Votes fermés" msgstr "Votes fermés"
@@ -4183,6 +4188,10 @@ msgstr "au"
msgid "Polls open from" msgid "Polls open from"
msgstr "Votes ouverts du" msgstr "Votes ouverts du"
#: election/views.py
msgid "Form is invalid"
msgstr "Formulaire invalide"
#: forum/models.py #: forum/models.py
msgid "is a category" msgid "is a category"
msgstr "est une catégorie" msgstr "est une catégorie"