diff --git a/counter/views/admin.py b/counter/views/admin.py
index 9737bb16..e5e13306 100644
--- a/counter/views/admin.py
+++ b/counter/views/admin.py
@@ -48,7 +48,7 @@ from counter.models import (
Selling,
)
from counter.utils import is_logged_in_counter
-from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin
+from counter.views.mixins import CounterAdminTabsMixin
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
@@ -84,17 +84,18 @@ class CounterEditView(
return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id})
-class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
- """Edit a counter's main informations (for the counter's admin)."""
+class CounterEditPropView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
+ """Edit a counter's main infos."""
model = Counter
form_class = modelform_factory(Counter, fields=["name", "club", "type"])
pk_url_kwarg = "counter_id"
template_name = "core/edit.jinja"
current_tab = "counters"
+ permission_required = "counter.change_counter"
-class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
+class CounterCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
"""Create a counter (for the admins)."""
model = Counter
@@ -105,9 +106,10 @@ class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
)
template_name = "core/create.jinja"
current_tab = "counters"
+ permission_required = "counter.add_counter"
-class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
+class CounterDeleteView(CounterAdminTabsMixin, PermissionRequiredMixin, DeleteView):
"""Delete a counter (for the admins)."""
model = Counter
@@ -115,30 +117,33 @@ class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("counter:admin_list")
current_tab = "counters"
+ permission_required = "counter.delete_counter"
# Product management
-class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
+class ProductTypeListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView):
"""A list view for the admins."""
model = ProductType
template_name = "counter/product_type_list.jinja"
current_tab = "product_types"
context_object_name = "product_types"
+ permission_required = "counter.view_producttype"
-class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
+class ProductTypeCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
"""A create view for the admins."""
model = ProductType
fields = ["name", "description", "comment", "icon"]
template_name = "core/create.jinja"
current_tab = "products"
+ permission_required = "counter.add_producttype"
-class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
+class ProductTypeEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
"""An edit view for the admins."""
model = ProductType
@@ -146,23 +151,26 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
fields = ["name", "description", "comment", "icon"]
pk_url_kwarg = "type_id"
current_tab = "products"
+ permission_required = "counter.change_producttype"
-class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
+class ProductListView(CounterAdminTabsMixin, PermissionRequiredMixin, TemplateView):
current_tab = "products"
template_name = "counter/product_list.jinja"
+ permission_required = "counter.view_product"
-class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
+class ProductCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
"""A create view for the admins."""
model = Product
form_class = ProductForm
template_name = "counter/product_form.jinja"
current_tab = "products"
+ permission_required = "counter.add_product"
-class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
+class ProductEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
"""An edit view for the admins."""
model = Product
@@ -170,6 +178,13 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
pk_url_kwarg = "product_id"
template_name = "counter/product_form.jinja"
current_tab = "products"
+ permission_required = "counter.change_product"
+
+ def has_permission(self):
+ return self.request.user.has_perm("counter.change_product") or (
+ self.request.method == "GET"
+ and self.request.user.has_perm("counter.view_product")
+ )
class ProductFormulaListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView):
@@ -299,7 +314,6 @@ class RefillingDeleteView(DeleteView):
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
- """We have here a very particular right handling, we can't inherit from CanEditPropMixin."""
self.object = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
@@ -324,7 +338,6 @@ class SellingDeleteView(DeleteView):
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
- """We have here a very particular right handling, we can't inherit from CanEditPropMixin."""
self.object = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
@@ -376,23 +389,23 @@ class CounterStatView(PermissionRequiredMixin, DetailView):
return kwargs
-class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
+class CounterRefillingListView(
+ CounterAdminTabsMixin, PermissionRequiredMixin, ListView
+):
"""List of refillings on a counter."""
model = Refilling
template_name = "counter/refilling_list.jinja"
current_tab = "counters"
paginate_by = 30
+ permission_required = "counter.view_refilling"
- def dispatch(self, request, *args, **kwargs):
- self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"])
- self.queryset = Refilling.objects.filter(counter__id=self.counter.id)
- return super().dispatch(request, *args, **kwargs)
+ def get_queryset(self):
+ self.counter = get_object_or_404(Counter, pk=self.kwargs["counter_id"])
+ return Refilling.objects.filter(counter=self.counter).order_by("-date")
def get_context_data(self, **kwargs):
- kwargs = super().get_context_data(**kwargs)
- kwargs["counter"] = self.counter
- return kwargs
+ return super().get_context_data(**kwargs) | {"counter": self.counter}
class RefoundAccountView(UserPassesTestMixin, FormView):
diff --git a/counter/views/eticket.py b/counter/views/eticket.py
index c5b4b872..09b72d41 100644
--- a/counter/views/eticket.py
+++ b/counter/views/eticket.py
@@ -12,7 +12,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
-
+from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import Http404, HttpResponse
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
@@ -21,28 +21,30 @@ from django.views.generic.edit import CreateView, UpdateView
from core.auth.mixins import CanViewMixin
from counter.forms import EticketForm
from counter.models import Eticket, Selling
-from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin
+from counter.views.mixins import CounterAdminTabsMixin
-class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
+class EticketListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView):
"""A list view for the admins."""
model = Eticket
template_name = "counter/eticket_list.jinja"
ordering = ["id"]
current_tab = "etickets"
+ permission_required = "counter.view_eticket"
-class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
+class EticketCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
"""Create an eticket."""
model = Eticket
template_name = "core/create.jinja"
form_class = EticketForm
current_tab = "etickets"
+ permission_required = "counter.add_eticket"
-class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
+class EticketEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
"""Edit an eticket."""
model = Eticket
@@ -50,6 +52,7 @@ class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
form_class = EticketForm
pk_url_kwarg = "eticket_id"
current_tab = "etickets"
+ permission_required = "counter.change_eticket"
class EticketPDFView(CanViewMixin, DetailView):
diff --git a/counter/views/mixins.py b/counter/views/mixins.py
index 2cce25b4..8257833e 100644
--- a/counter/views/mixins.py
+++ b/counter/views/mixins.py
@@ -13,38 +13,13 @@
#
#
-from django.conf import settings
-from django.core.exceptions import PermissionDenied
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
-from django.views.generic.base import View
from core.views.mixins import TabedViewMixin
from counter.utils import is_logged_in_counter
-class CounterAdminMixin(View):
- """Protect counter admin section."""
-
- edit_group = [settings.SITH_GROUP_COUNTER_ADMIN_ID]
- edit_club = []
-
- def _test_group(self, user):
- return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
-
- def _test_club(self, user):
- return any(c.can_be_edited_by(user) for c in self.edit_club)
-
- def dispatch(self, request, *args, **kwargs):
- if not (
- request.user.is_root
- or self._test_group(request.user)
- or self._test_club(request.user)
- ):
- raise PermissionDenied
- return super().dispatch(request, *args, **kwargs)
-
-
class CounterTabsMixin(TabedViewMixin):
def get_tabs_title(self):
return self.object
@@ -89,45 +64,70 @@ class CounterTabsMixin(TabedViewMixin):
class CounterAdminTabsMixin(TabedViewMixin):
tabs_title = _("Counter administration")
- list_of_tabs = [
- {
- "url": reverse_lazy("counter:admin_list"),
- "slug": "counters",
- "name": _("Counters"),
- },
- {
- "url": reverse_lazy("counter:product_list"),
- "slug": "products",
- "name": _("Products"),
- },
- {
- "url": reverse_lazy("counter:product_formula_list"),
- "slug": "formulas",
- "name": _("Formulas"),
- },
- {
- "url": reverse_lazy("counter:product_type_list"),
- "slug": "product_types",
- "name": _("Product types"),
- },
- {
- "url": reverse_lazy("counter:returnable_list"),
- "slug": "returnable_products",
- "name": _("Returnable products"),
- },
- {
- "url": reverse_lazy("counter:cash_summary_list"),
- "slug": "cash_summary",
- "name": _("Cash register summaries"),
- },
- {
- "url": reverse_lazy("counter:invoices_call"),
- "slug": "invoices_call",
- "name": _("Invoices call"),
- },
- {
- "url": reverse_lazy("counter:eticket_list"),
- "slug": "etickets",
- "name": _("Etickets"),
- },
- ]
+
+ def get_list_of_tabs(self):
+ user = self.request.user
+ res = [
+ {
+ "url": reverse_lazy("counter:admin_list"),
+ "slug": "counters",
+ "name": _("Counters"),
+ }
+ ]
+ if user.has_perm("counter.view_product"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:product_list"),
+ "slug": "products",
+ "name": _("Products"),
+ }
+ )
+ if user.has_perm("counter.view_productformula"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:product_formula_list"),
+ "slug": "formulas",
+ "name": _("Formulas"),
+ }
+ )
+ if user.has_perm("counter.view_producttype"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:product_type_list"),
+ "slug": "product_types",
+ "name": _("Product types"),
+ }
+ )
+ if user.has_perm("counter.view_returnableproduct"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:returnable_list"),
+ "slug": "returnable_products",
+ "name": _("Returnable products"),
+ }
+ )
+ if user.has_perm("counter.view_cashregistersummary"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:cash_summary_list"),
+ "slug": "cash_summary",
+ "name": _("Cash register summaries"),
+ }
+ )
+ if user.has_perm("counter.view_invoicecall"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:invoices_call"),
+ "slug": "invoices_call",
+ "name": _("Invoices call"),
+ }
+ )
+ if user.has_perm("counter.view_eticket"):
+ res.append(
+ {
+ "url": reverse_lazy("counter:eticket_list"),
+ "slug": "etickets",
+ "name": _("Etickets"),
+ }
+ )
+ return res
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index b4714ca1..eadaed9e 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: 2026-10-02 12:56+0200\n"
+"POT-Creation-Date: 2026-10-09 21:44+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal \n"
@@ -236,7 +236,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 subscription/forms.py
+#: club/forms.py com/forms.py counter/forms.py
msgid "End date"
msgstr "Date de fin"
@@ -1093,7 +1093,7 @@ msgstr "Prix d'achat"
msgid "Format: 16:9 | Resolution: 1920x1080"
msgstr "Format : 16:9 | Résolution : 1920x1080"
-#: com/forms.py subscription/forms.py
+#: com/forms.py
msgid "Start date"
msgstr "Date de début"
@@ -4102,7 +4102,6 @@ msgstr "Nouvelle formule"
#: counter/templates/counter/fragments/create_student_card.jinja
#: counter/templates/counter/invoices_call.jinja
#: sas/templates/sas/picture.jinja
-#: subscription/templates/subscription/stats.jinja
msgid "Go"
msgstr "Valider"
@@ -4237,9 +4236,12 @@ msgid "Remove price"
msgstr "Retirer le prix"
#: counter/templates/counter/product_form.jinja
-#, python-format
-msgid "Edit product %(name)s"
-msgstr "Édition du produit %(name)s"
+msgid ""
+"You can see product details. However, you cannot edit it, thus you won't be "
+"able to submit this form."
+msgstr ""
+"Vous pouvez voir ce produit. Cependant, vous ne pouvez pas l'éditer ; vous "
+"ne serez donc pas en mesure de soumettre le formulaire."
#: counter/templates/counter/product_form.jinja
msgid "Creation date"
@@ -5985,12 +5987,8 @@ msgid "Create another subscription"
msgstr "Créer une nouvelle cotisation"
#: subscription/templates/subscription/stats.jinja
-msgid "Total subscriptions"
-msgstr "Cotisations totales"
-
-#: subscription/templates/subscription/stats.jinja
-msgid "Subscriptions by type"
-msgstr "Cotisations par type"
+msgid "All subscriptions"
+msgstr "Toutes les cotisations"
#: subscription/templates/subscription/subscription.jinja
msgid "Existing member"