-
- {%- if role.max_choice == 1 and election.can_vote(user) %}
+
+ {%- if role.max_choice == 1 and show_vote_buttons %}
-
-
+ {% set input_id = "blank_vote_" + role.id|string %}
+
+
{% trans %}Choose blank vote{% endtrans %}
- {%- set _ = count.append(count.pop() + 1) %}
{%- endif %}
{%- if election.is_vote_finished %}
{%- set results = election_results[role.title]['blank vote'] %}
@@ -120,13 +131,14 @@
{%- endif %}
{%- for election_list in election_lists %}
-
+
- {%- for candidature in election_list.candidatures.filter(role=role) %}
+ {%- for candidature in election_list.candidatures.select_related("user", "user__profile_pict").filter(role=role) %}
- {%- if election.can_vote(user) %}
-
-
+ {%- if show_vote_buttons %}
+ {% set input_id = "candidature_" + candidature.id|string %}
+
+
{%- endif %}
{%- if user.is_subscriber_viewable %}
@@ -140,7 +152,7 @@
{{ candidature.user.first_name }} {{candidature.user.nick_name or ''}} {{ candidature.user.last_name }}
{%- if not election.is_vote_finished %}
- {{ candidature.program|markdown or '' }}
+ {{ candidature.program|markdown }}
{%- endif %}
@@ -153,9 +165,8 @@
{%- endif -%}
{%- endif -%}
- {%- if election.can_vote(user) %}
+ {%- if show_vote_buttons %}
- {%- set _ = count.append(count.pop() + 1) %}
{%- endif %}
{%- if election.is_vote_finished %}
{%- set results = election_results[role.title][candidature.user.username] %}
@@ -191,36 +202,9 @@
{% trans %}Delete{% endtrans %}
{%- endif %}
- {%- if not election.has_voted(user) and election.can_vote(user) %}
+ {%- if show_vote_buttons %}
{%- endif %}
{% endblock %}
-
-{% block script %}
- {{ super() }}
-
-{% endblock %}
diff --git a/election/tests.py b/election/tests.py
index f4cc380e..45ac3ea7 100644
--- a/election/tests.py
+++ b/election/tests.py
@@ -1,9 +1,15 @@
-from django.conf import settings
-from django.test import TestCase
-from django.urls import reverse
+from datetime import timedelta
+import pytest
+from django.conf import settings
+from django.test import Client, TestCase
+from django.urls import reverse
+from django.utils.timezone import now
+from model_bakery import baker
+
+from core.baker_recipes import subscriber_user
from core.models import Group, User
-from election.models import Election
+from election.models import Candidature, Election, ElectionList, Role, Vote
class TestElection(TestCase):
@@ -12,8 +18,7 @@ class TestElection(TestCase):
cls.election = Election.objects.first()
cls.public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID)
cls.sli = User.objects.get(username="sli")
- cls.subscriber = User.objects.get(username="subscriber")
- cls.public = User.objects.get(username="public")
+ cls.public = baker.make(User)
class TestElectionDetail(TestElection):
@@ -36,7 +41,7 @@ class TestElectionDetail(TestElection):
class TestElectionUpdateView(TestElection):
def test_permission_denied(self):
- self.client.force_login(self.subscriber)
+ self.client.force_login(subscriber_user.make())
response = self.client.get(
reverse("election:update", args=str(self.election.id))
)
@@ -45,3 +50,68 @@ class TestElectionUpdateView(TestElection):
reverse("election:update", args=str(self.election.id))
)
assert response.status_code == 403
+
+
+@pytest.mark.django_db
+def test_election_create_list_permission(client: Client):
+ election = baker.make(Election, end_candidature=now() + timedelta(hours=1))
+ groups = [
+ Group.objects.get(pk=settings.SITH_GROUP_SUBSCRIBERS_ID),
+ baker.make(Group),
+ ]
+ election.candidature_groups.add(groups[0])
+ election.edit_groups.add(groups[1])
+ url = reverse("election:create_list", kwargs={"election_id": election.id})
+ for user in subscriber_user.make(), baker.make(User, groups=[groups[1]]):
+ client.force_login(user)
+ assert client.get(url).status_code == 200
+ # the post is a 200 instead of a 302, because we don't give form data,
+ # but we don't care as we only test permissions here
+ assert client.post(url).status_code == 200
+ client.force_login(baker.make(User))
+ assert client.get(url).status_code == 403
+ assert client.post(url).status_code == 403
+
+
+@pytest.mark.django_db
+def test_election_results():
+ election = baker.make(
+ Election, voters=baker.make(User, _quantity=50, _bulk_create=True)
+ )
+ lists = baker.make(ElectionList, election=election, _quantity=2, _bulk_create=True)
+ roles = baker.make(
+ Role, election=election, max_choice=iter([1, 2]), _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]),
+ ]
+ votes = [
+ baker.make(Vote, role=roles[0], _quantity=20, _bulk_create=True),
+ baker.make(Vote, role=roles[0], _quantity=25, _bulk_create=True),
+ baker.make(Vote, role=roles[1], _quantity=20, _bulk_create=True),
+ baker.make(Vote, role=roles[1], _quantity=35, _bulk_create=True),
+ baker.make(Vote, role=roles[1], _quantity=10, _bulk_create=True),
+ ]
+ cand[0].votes.set(votes[0])
+ cand[1].votes.set(votes[1])
+ cand[2].votes.set([*votes[2], *votes[4]])
+ cand[3].votes.set([*votes[3], *votes[4]])
+
+ assert election.results == {
+ roles[0].title: {
+ cand[0].user.username: {"percent": 40.0, "vote": 20},
+ cand[1].user.username: {"percent": 50.0, "vote": 25},
+ "blank vote": {"percent": 10.0, "vote": 5},
+ "total vote": 50,
+ },
+ roles[1].title: {
+ cand[2].user.username: {"percent": 30.0, "vote": 30},
+ cand[3].user.username: {"percent": 45.0, "vote": 45},
+ "blank vote": {"percent": 25.0, "vote": 25},
+ "total vote": 100,
+ },
+ }
diff --git a/election/views.py b/election/views.py
index 25866422..189c34d5 100644
--- a/election/views.py
+++ b/election/views.py
@@ -1,183 +1,34 @@
from typing import TYPE_CHECKING
-from django import forms
-from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
+from cryptography.utils import cached_property
+from django.conf import settings
+from django.contrib.auth.mixins import (
+ LoginRequiredMixin,
+ PermissionRequiredMixin,
+ UserPassesTestMixin,
+)
from django.core.exceptions import PermissionDenied
from django.db import transaction
-from django.db.models.query import QuerySet
-from django.shortcuts import get_object_or_404, redirect
+from django.db.models import QuerySet
+from django.shortcuts import get_object_or_404
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.edit import CreateView, DeleteView, FormView, UpdateView
-from core.auth.mixins import CanCreateMixin, CanEditMixin, CanViewMixin
-from core.views.forms import SelectDateTime
-from core.views.widgets.ajax_select import (
- AutoCompleteSelect,
- AutoCompleteSelectMultipleGroup,
- AutoCompleteSelectUser,
+from core.auth.mixins import CanEditMixin, CanViewMixin
+from election.forms import (
+ CandidateForm,
+ ElectionForm,
+ ElectionListForm,
+ RoleForm,
+ VoteForm,
)
-from core.views.widgets.markdown import MarkdownInput
from election.models import Candidature, Election, ElectionList, Role, Vote
if TYPE_CHECKING:
from core.models import User
-# Custom form field
-
-
-class LimitedCheckboxField(forms.ModelMultipleChoiceField):
- """A `ModelMultipleChoiceField`, with a max limit of selectable inputs."""
-
- def __init__(self, queryset, max_choice, **kwargs):
- self.max_choice = max_choice
- super().__init__(queryset, **kwargs)
-
- def clean(self, value):
- qs = super().clean(value)
- self.validate(qs)
- return qs
-
- def validate(self, qs):
- if qs.count() > self.max_choice:
- raise forms.ValidationError(
- _("You have selected too much candidates."), code="invalid"
- )
-
-
-# Forms
-
-
-class CandidateForm(forms.ModelForm):
- """Form to candidate."""
-
- class Meta:
- model = Candidature
- fields = ["user", "role", "program", "election_list"]
- labels = {
- "user": _("User to candidate"),
- }
- widgets = {
- "program": MarkdownInput,
- "user": AutoCompleteSelectUser,
- "role": AutoCompleteSelect,
- "election_list": AutoCompleteSelect,
- }
-
- def __init__(self, *args, **kwargs):
- election_id = kwargs.pop("election_id", None)
- can_edit = kwargs.pop("can_edit", False)
- super().__init__(*args, **kwargs)
- if election_id:
- self.fields["role"].queryset = Role.objects.filter(
- election__id=election_id
- ).all()
- self.fields["election_list"].queryset = ElectionList.objects.filter(
- election__id=election_id
- ).all()
- if not can_edit:
- self.fields["user"].widget = forms.HiddenInput()
-
-
-class VoteForm(forms.Form):
- def __init__(self, election, 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"),
- )
-
-
-class RoleForm(forms.ModelForm):
- """Form for creating a role."""
-
- class Meta:
- model = Role
- fields = ["title", "election", "description", "max_choice"]
- widgets = {"election": AutoCompleteSelect}
-
- def __init__(self, *args, **kwargs):
- election_id = kwargs.pop("election_id", None)
- super().__init__(*args, **kwargs)
- if election_id:
- self.fields["election"].queryset = Election.objects.filter(
- id=election_id
- ).all()
-
- def clean(self):
- cleaned_data = super().clean()
- title = cleaned_data.get("title")
- election = cleaned_data.get("election")
- if Role.objects.filter(title=title, election=election).exists():
- raise forms.ValidationError(
- _("This role already exists for this election"), code="invalid"
- )
-
-
-class ElectionListForm(forms.ModelForm):
- class Meta:
- model = ElectionList
- fields = ("title", "election")
- widgets = {"election": AutoCompleteSelect}
-
- def __init__(self, *args, **kwargs):
- election_id = kwargs.pop("election_id", None)
- super().__init__(*args, **kwargs)
- if election_id:
- self.fields["election"].queryset = Election.objects.filter(
- id=election_id
- ).all()
-
-
-class ElectionForm(forms.ModelForm):
- class Meta:
- model = Election
- fields = [
- "title",
- "description",
- "archived",
- "start_candidature",
- "end_candidature",
- "start_date",
- "end_date",
- "edit_groups",
- "view_groups",
- "vote_groups",
- "candidature_groups",
- ]
- widgets = {
- "edit_groups": AutoCompleteSelectMultipleGroup,
- "view_groups": AutoCompleteSelectMultipleGroup,
- "vote_groups": AutoCompleteSelectMultipleGroup,
- "candidature_groups": AutoCompleteSelectMultipleGroup,
- }
-
- start_date = forms.DateTimeField(
- label=_("Start date"), widget=SelectDateTime, required=True
- )
- end_date = forms.DateTimeField(
- label=_("End date"), widget=SelectDateTime, required=True
- )
- start_candidature = forms.DateTimeField(
- label=_("Start candidature"), widget=SelectDateTime, required=True
- )
- end_candidature = forms.DateTimeField(
- label=_("End candidature"), widget=SelectDateTime, required=True
- )
-
-
# Display elections
@@ -185,25 +36,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."""
@@ -212,46 +59,67 @@ 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
-class VoteFormView(CanCreateMixin, FormView):
+class VoteFormView(LoginRequiredMixin, UserPassesTestMixin, FormView):
"""Alows users to vote."""
form_class = VoteForm
template_name = "election/election_detail.jinja"
- def dispatch(self, request, *arg, **kwargs):
- self.election = get_object_or_404(Election, pk=kwargs["election_id"])
- return super().dispatch(request, *arg, **kwargs)
+ @cached_property
+ def election(self):
+ return get_object_or_404(Election, pk=self.kwargs["election_id"])
+
+ def test_func(self):
+ groups = set(self.election.vote_groups.values_list("id", flat=True))
+ if (
+ settings.SITH_GROUP_SUBSCRIBERS_ID in groups
+ and self.request.user.is_subscribed
+ ):
+ # the subscriber group isn't truly attached to users,
+ # so it must be dealt with separately
+ return True
+ return self.request.user.groups.filter(id__in=groups).exists()
def vote(self, election_data):
with transaction.atomic():
@@ -271,20 +139,16 @@ class VoteFormView(CanCreateMixin, FormView):
self.election.voters.add(self.request.user)
def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs["election"] = self.election
- kwargs["user"] = self.request.user
- return kwargs
+ return super().get_form_kwargs() | {
+ "election": self.election,
+ "user": self.request.user,
+ }
def form_valid(self, form):
"""Verify that the user is part in a vote group."""
data = form.clean()
- res = super(FormView, self).form_valid(form)
- for grp_id in self.election.vote_groups.values_list("pk", flat=True):
- if self.request.user.is_in_group(pk=grp_id):
- self.vote(data)
- return res
- return res
+ self.vote(data)
+ return super().form_valid(form)
def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.election.id})
@@ -310,26 +174,22 @@ class CandidatureCreateView(LoginRequiredMixin, CreateView):
def dispatch(self, request, *arg, **kwargs):
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)
def get_initial(self):
- init = {}
- self.can_edit = self.request.user.can_edit(self.election)
- init["user"] = self.request.user.id
- return init
+ return {"user": self.request.user.id}
def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs["election_id"] = self.election.id
- kwargs["can_edit"] = self.can_edit
- return kwargs
+ return super().get_form_kwargs() | {
+ "election": self.election,
+ "can_edit": self.can_edit,
+ }
- def form_valid(self, form):
+ def form_valid(self, form: CandidateForm):
"""Verify that the selected user is in candidate group."""
obj = form.instance
obj.election = self.election
- if not hasattr(obj, "user"):
- obj.user = self.request.user
if (obj.election.can_candidate(obj.user)) and (
obj.user == self.request.user or self.can_edit
):
@@ -337,9 +197,7 @@ class CandidatureCreateView(LoginRequiredMixin, CreateView):
raise PermissionDenied
def get_context_data(self, **kwargs):
- kwargs = super().get_context_data(**kwargs)
- kwargs["election"] = self.election
- return kwargs
+ return super().get_context_data(**kwargs) | {"election": self.election}
def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.election.id})
@@ -355,80 +213,79 @@ class ElectionCreateView(PermissionRequiredMixin, CreateView):
return reverse("election:detail", kwargs={"election_id": self.object.id})
-class RoleCreateView(CanCreateMixin, CreateView):
+class RoleCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = Role
form_class = RoleForm
template_name = "core/create.jinja"
- def dispatch(self, request, *arg, **kwargs):
- self.election = get_object_or_404(Election, pk=kwargs["election_id"])
+ @cached_property
+ def election(self):
+ return get_object_or_404(Election, pk=self.kwargs["election_id"])
+
+ def test_func(self):
if not self.election.is_vote_editable:
- raise PermissionDenied
- return super().dispatch(request, *arg, **kwargs)
+ return False
+ if self.request.user.has_perm("election.add_role"):
+ return True
+ groups = set(self.election.edit_groups.values_list("id", flat=True))
+ if (
+ settings.SITH_GROUP_SUBSCRIBERS_ID in groups
+ and self.request.user.is_subscribed
+ ):
+ # the subscriber group isn't truly attached to users,
+ # so it must be dealt with separately
+ return True
+ return self.request.user.groups.filter(id__in=groups).exists()
def get_initial(self):
- init = {}
- init["election"] = self.election
- return init
-
- def form_valid(self, form):
- """Verify that the user can edit properly."""
- obj: Role = form.instance
- user: User = self.request.user
- if obj.election:
- for grp_id in obj.election.edit_groups.values_list("pk", flat=True):
- if user.is_in_group(pk=grp_id):
- return super(CreateView, self).form_valid(form)
- raise PermissionDenied
+ return {"election": self.election}
def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs["election_id"] = self.election.id
- return kwargs
+ return super().get_form_kwargs() | {"election_id": self.election.id}
def get_success_url(self, **kwargs):
- return reverse_lazy(
- "election:detail", kwargs={"election_id": self.object.election.id}
+ return reverse(
+ "election:detail", kwargs={"election_id": self.object.election_id}
)
-class ElectionListCreateView(CanCreateMixin, CreateView):
+class ElectionListCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = ElectionList
form_class = ElectionListForm
template_name = "core/create.jinja"
- def dispatch(self, request, *arg, **kwargs):
- self.election = get_object_or_404(Election, pk=kwargs["election_id"])
+ @cached_property
+ def election(self):
+ return get_object_or_404(Election, pk=self.kwargs["election_id"])
+
+ def test_func(self):
if not self.election.is_vote_editable:
- raise PermissionDenied
- return super().dispatch(request, *arg, **kwargs)
+ return False
+ if self.request.user.has_perm("election.add_electionlist"):
+ return True
+ groups = set(
+ self.election.candidature_groups.values("id")
+ .union(self.election.edit_groups.values("id"))
+ .values_list("id", flat=True)
+ )
+ if (
+ settings.SITH_GROUP_SUBSCRIBERS_ID in groups
+ and self.request.user.is_subscribed
+ ):
+ # the subscriber group isn't truly attached to users,
+ # so it must be dealt with separately
+ return True
+ return self.request.user.groups.filter(id__in=groups).exists()
def get_initial(self):
- init = {}
- init["election"] = self.election
- return init
+ return {"election": self.election}
def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs["election_id"] = self.election.id
- return kwargs
-
- def form_valid(self, form):
- """Verify that the user can vote on this election."""
- obj: ElectionList = form.instance
- user: User = self.request.user
- if obj.election:
- for grp_id in obj.election.candidature_groups.values_list("pk", flat=True):
- if user.is_in_group(pk=grp_id):
- return super(CreateView, self).form_valid(form)
- for grp_id in obj.election.edit_groups.values_list("pk", flat=True):
- if user.is_in_group(pk=grp_id):
- return super(CreateView, self).form_valid(form)
- raise PermissionDenied
+ return super().get_form_kwargs() | {"election_id": self.election.id}
def get_success_url(self, **kwargs):
- return reverse_lazy(
- "election:detail", kwargs={"election_id": self.object.election.id}
+ return reverse(
+ "election:detail", kwargs={"election_id": self.object.election_id}
)
@@ -457,45 +314,23 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
-class CandidatureUpdateView(CanEditMixin, UpdateView):
+class CandidatureUpdateView(LoginRequiredMixin, CanEditMixin, UpdateView):
model = Candidature
form_class = CandidateForm
template_name = "core/edit.jinja"
pk_url_kwarg = "candidature_id"
- def dispatch(self, request, *arg, **kwargs):
- self.object = self.get_object()
- if not self.object.role.election.is_vote_editable:
- raise PermissionDenied
- 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(self, *args, **kwargs):
+ form = super().get_form(*args, **kwargs)
+ form.fields.pop("role", None)
+ return form
def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs["election_id"] = self.object.role.election.id
- return kwargs
+ return super().get_form_kwargs() | {"election": self.object.role.election}
def get_success_url(self, **kwargs):
- return reverse_lazy(
- "election:detail", kwargs={"election_id": self.object.role.election.id}
+ return reverse(
+ "election:detail", kwargs={"election_id": self.object.role.election_id}
)
@@ -546,18 +381,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):
@@ -573,7 +402,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):
@@ -589,7 +418,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):
@@ -605,4 +434,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})
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 19b164df..6d22e9c9 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -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 \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"