Compare commits

..

4 Commits

Author SHA1 Message Date
thomas girod
5af894060a Merge pull request #1273 from ae-utbm/fix-counter
fix: wrong quantity displayed on click after removing item
2026-01-21 22:42:27 +01:00
679b8dac1c Merge pull request #1278 from ae-utbm/download-picture-fix
Fix image file generation on user image download
2026-01-21 22:04:09 +01:00
Sli
e9eb3dc17d Fix image file generation on user image download
* Add image id on the name to avoid error with images with the exact same date (if we have epoch for example)
* Fix album name due to schema change not reflected here
2026-01-07 17:49:14 +01:00
imperosol
53a3dc0060 fix: wrong quantity displayed on click after removing item 2025-12-20 06:47:29 +01:00
7 changed files with 32 additions and 127 deletions

View File

@@ -104,7 +104,7 @@
</div> </div>
<ul> <ul>
<li x-show="getBasketSize() === 0">{% trans %}This basket is empty{% endtrans %}</li> <li x-show="getBasketSize() === 0">{% trans %}This basket is empty{% endtrans %}</li>
<template x-for="(item, index) in Object.values(basket)"> <template x-for="(item, index) in Object.values(basket)" :key="item.product.id">
<li> <li>
<template x-for="error in item.errors"> <template x-for="error in item.errors">
<div class="alert alert-red" x-text="error"> <div class="alert alert-red" x-text="error">

View File

@@ -60,6 +60,8 @@ 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:
@@ -72,7 +74,6 @@ 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,11 +14,6 @@
{% 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">
@@ -122,7 +117,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 }}" value="" checked> <input id="{{ input_id }}" type="radio" name="{{ role.title }}">
<label for="{{ input_id }}"> <label for="{{ input_id }}">
<span>{% trans %}Choose blank vote{% endtrans %}</span> <span>{% trans %}Choose blank vote{% endtrans %}</span>
</label> </label>
@@ -190,7 +185,6 @@
</table> </table>
</form> </form>
</section> </section>
{% if not user.is_anonymous %}
<section class="buttons"> <section class="buttons">
{%- if (election.can_candidate(user) and election.is_candidature_active) or (user.can_edit(election) and election.is_vote_editable) %} {%- 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> <a class="button" href="{{ url('election:candidate', election_id=object.id) }}">{% trans %}Candidate{% endtrans %}</a>
@@ -213,5 +207,4 @@
<button class="button button_send" form="vote-form">{% trans %}Submit the vote !{% endtrans %}</button> <button class="button button_send" form="vote-form">{% trans %}Submit the vote !{% endtrans %}</button>
</section> </section>
{%- endif %} {%- endif %}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -115,72 +115,3 @@ def test_election_results():
"total vote": 100, "total vote": 100,
}, },
} }
@pytest.mark.django_db
def test_election_form(client : Client):
election = baker.make(
Election,
end_date = now() + timedelta(days=1),
)
group = baker.make(Group)
election.vote_groups.add(group)
lists = baker.make(ElectionList, election=election, _quantity=2, _bulk_create=True)
roles = baker.make(Role, election=election, _quantity=2, _bulk_create=True)
users = baker.make(User, _quantity=4, _bulk_create=True)
cand = [
baker.make(Candidature, role=roles[0], user=users[0], election_list=lists[0]),
baker.make(Candidature, role=roles[0], user=users[1], election_list=lists[1]),
baker.make(Candidature, role=roles[1], user=users[2], election_list=lists[0]),
baker.make(Candidature, role=roles[1], user=users[3], election_list=lists[1]),
]
url = reverse("election:vote", kwargs={"election_id": election.id})
votes = [
{
roles[0].title : "",
roles[1].title : str(cand[2].id),
},
{
roles[0].title : "",
roles[1].title : "",
},
{
roles[0].title : str(cand[0].id),
roles[1].title : str(cand[2].id),
},
{
roles[0].title : str(cand[0].id),
roles[1].title : str(cand[3].id),
},
]
NB_VOTER = len(votes)
voters = [subscriber_user.make() for _ in range(NB_VOTER)]
for i in range(NB_VOTER):
voter = voters[i]
voter.groups.add(group)
if not election.can_vote(voter):
assert False
client.force_login(voter)
reponse = client.post(url, data = votes[i])
assert reponse.status_code == 302
assert election.results == {
roles[0].title: {
cand[0].user.username: {"percent": 50.0, "vote": 2},
cand[1].user.username: {"percent": 0.0, "vote": 0},
"blank vote": {"percent": 50.0, "vote": 2},
"total vote": 4,
},
roles[1].title: {
cand[2].user.username: {"percent": 50.0, "vote": 2},
cand[3].user.username: {"percent": 25.0, "vote": 1},
"blank vote": {"percent": 25.0, "vote": 1},
"total vote": 4,
},
}

View File

@@ -2,7 +2,6 @@ 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,
@@ -11,9 +10,8 @@ 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, redirect from django.shortcuts import get_object_or_404
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
@@ -55,7 +53,7 @@ class ElectionListArchivedView(CanViewMixin, ListView):
class ElectionDetailView(CanViewMixin, DetailView): class ElectionDetailView(CanViewMixin, DetailView):
"""Details an election responsibility by responsibility.""" """Details an election responsability by responsability."""
model = Election model = Election
template_name = "election/election_detail.jinja" template_name = "election/election_detail.jinja"
@@ -85,7 +83,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 additional data to the template.""" """Add additionnal 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),
@@ -103,7 +101,7 @@ class ElectionDetailView(CanViewMixin, DetailView):
class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView): class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView):
"""Allows users to vote.""" """Alows users to vote."""
form_class = VoteForm form_class = VoteForm
template_name = "election/election_detail.jinja" template_name = "election/election_detail.jinja"
@@ -113,9 +111,6 @@ 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
@@ -155,17 +150,11 @@ 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 additional data to the template.""" """Add additionnal 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: 2026-01-14 11:34+0100\n" "POT-Creation-Date: 2025-12-19 23:10+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,11 +4108,6 @@ 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"
@@ -4188,10 +4183,6 @@ 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"

View File

@@ -31,7 +31,7 @@ document.addEventListener("alpine:init", () => {
await Promise.all( await Promise.all(
this.downloadPictures.map(async (p: PictureSchema) => { this.downloadPictures.map(async (p: PictureSchema) => {
const imgName = `${p.album}/IMG_${p.date.replace(/[:\-]/g, "_")}${p.name.slice(p.name.lastIndexOf("."))}`; const imgName = `${p.album.name}/IMG_${p.id}_${p.date.replace(/[:\-]/g, "_")}${p.name.slice(p.name.lastIndexOf("."))}`;
return zipWriter.add(imgName, new HttpReader(p.full_size_url), { return zipWriter.add(imgName, new HttpReader(p.full_size_url), {
level: 9, level: 9,
lastModDate: new Date(p.date), lastModDate: new Date(p.date),