diff --git a/counter/forms.py b/counter/forms.py index 6a1b83e4..1d9fa8a0 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -24,6 +24,7 @@ from core.views.forms import ( ) from core.views.widgets.ajax_select import ( AutoCompleteSelect, + AutoCompleteSelectMultiple, AutoCompleteSelectMultipleGroup, AutoCompleteSelectMultipleUser, AutoCompleteSelectUser, @@ -170,11 +171,21 @@ class CounterEditForm(forms.ModelForm): class Meta: model = Counter fields = ["sellers", "products"] + widgets = {"sellers": AutoCompleteSelectMultipleUser} - widgets = { - "sellers": AutoCompleteSelectMultipleUser, - "products": AutoCompleteSelectMultipleProduct, - } + def __init__(self, *args, user: User, instance: Counter, **kwargs): + super().__init__(*args, instance=instance, **kwargs) + if user.has_perm("counter.change_counter"): + self.fields["products"].widget = AutoCompleteSelectMultipleProduct() + else: + self.fields["products"].widget = AutoCompleteSelectMultiple() + self.fields["products"].queryset = Product.objects.filter( + Q(club_id=instance.club_id) | Q(counters=instance), archived=False + ).distinct() + self.fields["products"].help_text = _( + "If you want to add a product that is not owned by " + "your club to this counter, you should ask an admin." + ) class ScheduledProductActionForm(forms.ModelForm): diff --git a/counter/tests/test_counter_admin.py b/counter/tests/test_counter_admin.py new file mode 100644 index 00000000..eaade1c5 --- /dev/null +++ b/counter/tests/test_counter_admin.py @@ -0,0 +1,62 @@ +from django.contrib.auth.models import Permission +from django.test import TestCase +from model_bakery import baker + +from club.models import Membership +from core.baker_recipes import subscriber_user +from core.models import User +from counter.baker_recipes import product_recipe +from counter.forms import CounterEditForm +from counter.models import Counter + + +class TestEditCounterProducts(TestCase): + @classmethod + def setUpTestData(cls): + cls.counter = baker.make(Counter) + cls.products = product_recipe.make(_quantity=5, _bulk_create=True) + cls.counter.products.add(*cls.products) + + def test_admin(self): + """Test that an admin can add and remove products""" + user = baker.make( + User, user_permissions=[Permission.objects.get(codename="change_counter")] + ) + new_product = product_recipe.make() + form = CounterEditForm( + data={"sellers": [], "products": [*self.products[1:], new_product]}, + user=user, + instance=self.counter, + ) + assert form.is_valid() + form.save() + assert set(self.counter.products.all()) == {*self.products[1:], new_product} + + def test_club_board_id(self): + """Test that people from counter club board can only add their own products.""" + club = self.counter.club + user = subscriber_user.make() + baker.make(Membership, user=user, club=club, end_date=None) + new_product = product_recipe.make(club=club) + form = CounterEditForm( + data={"sellers": [], "products": [*self.products[1:], new_product]}, + user=user, + instance=self.counter, + ) + assert form.is_valid() + form.save() + assert set(self.counter.products.all()) == {*self.products[1:], new_product} + + new_product = product_recipe.make() # product not owned by the club + form = CounterEditForm( + data={"sellers": [], "products": [*self.products[1:], new_product]}, + user=user, + instance=self.counter, + ) + assert not form.is_valid() + assert form.errors == { + "products": [ + "Sélectionnez un choix valide. " + f"{new_product.id} n\u2019en fait pas partie." + ], + } diff --git a/counter/views/admin.py b/counter/views/admin.py index bd87150b..25990425 100644 --- a/counter/views/admin.py +++ b/counter/views/admin.py @@ -58,7 +58,7 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): current_tab = "counters" -class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): +class CounterEditView(CounterAdminTabsMixin, UserPassesTestMixin, UpdateView): """Edit a counter's main informations (for the counter's manager).""" model = Counter @@ -67,10 +67,14 @@ class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): template_name = "core/edit.jinja" current_tab = "counters" - def dispatch(self, request, *args, **kwargs): - obj = self.get_object() - self.edit_club.append(obj.club) - return super().dispatch(request, *args, **kwargs) + def test_func(self): + if self.request.user.has_perm("counter.change_counter"): + return True + obj = self.get_object(queryset=self.get_queryset().select_related("club")) + return obj.club.has_rights_in_club(self.request.user) + + def get_form_kwargs(self): + return super().get_form_kwargs() | {"user": self.request.user} def get_success_url(self): return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index c594b166..8600040b 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-02-14 15:21+0100\n" +"POT-Creation-Date: 2026-03-07 15:47+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -2937,6 +2937,14 @@ msgstr "Cet UID est invalide" msgid "User not found" msgstr "Utilisateur non trouvé" +#: counter/forms.py +msgid "" +"If you want to add a product that is not owned by your club to this counter, " +"you should ask an admin." +msgstr "" +"Si vous souhaitez ajouter sur ce comptoir un produit qui n'appartient pas à " +"votre club, vous devriez demander à un admin." + #: counter/forms.py msgid "Date and time of action" msgstr "Date et heure de l'action"