mirror of
https://github.com/ae-utbm/sith.git
synced 2025-02-21 06:57:11 +00:00
Add a page to manage the groups that can create permissions
This commit is contained in:
parent
2123e83010
commit
05d4a09f8c
@ -197,9 +197,9 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
<div>
|
||||
{% if user.is_root or user.is_board_member %}
|
||||
<form class="form-gifts" action="{{ url('core:user_gift_create', user_id=profile.id) }}" method="post">
|
||||
|
@ -30,11 +30,7 @@
|
||||
{% if user.has_perm("core.view_userban") %}
|
||||
<li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
{% if user.has_perm("subscription.add_subscription") %}
|
||||
<li><a href="{{ url('subscription:subscription') }}">{% trans %}Subscriptions{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_board_member or user.is_root %}
|
||||
<li><a href="{{ url('subscription:stats') }}">{% trans %}Subscription stats{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@ -163,6 +159,35 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user.has_perm("subscription.add_subscription") or user.has_perm("auth.change_perm") or user.is_root or user.is_board_member %}
|
||||
<div>
|
||||
<h4>{% trans %}Subscriptions{% endtrans %}</h4>
|
||||
<ul>
|
||||
{% if user.has_perm("subscription.add_subscription") %}
|
||||
<li>
|
||||
<a href="{{ url("subscription:subscription") }}">
|
||||
{% trans %}New subscription{% endtrans %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.has_perm("auth.change_permission") %}
|
||||
<li>
|
||||
<a href="{{ url("subscription:perms") }}">
|
||||
{% trans %}Manage permissions{% endtrans %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_root or user.is_board_member %}
|
||||
<li>
|
||||
<a href="{{ url("subscription:stats") }}">
|
||||
{% trans %}Subscription stats{% endtrans %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user.memberships.filter(end_date=None).all().count() > 0 %}
|
||||
<div>
|
||||
<h4>{% trans %}Club tools{% endtrans %}</h4>
|
||||
|
@ -28,6 +28,7 @@ from captcha.fields import CaptchaField
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.staticfiles.management.commands.collectstatic import (
|
||||
staticfiles_storage,
|
||||
)
|
||||
@ -440,3 +441,25 @@ class GiftForm(forms.ModelForm):
|
||||
id=user_id
|
||||
)
|
||||
self.fields["user"].widget = forms.HiddenInput()
|
||||
|
||||
|
||||
class PermissionGroupsForm(forms.ModelForm):
|
||||
"""Manage the groups that have a specific permission."""
|
||||
|
||||
class Meta:
|
||||
model = Permission
|
||||
fields = []
|
||||
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
Group.objects.all(), widget=AutoCompleteSelectMultipleGroup, required=False
|
||||
)
|
||||
|
||||
def __init__(self, instance: Permission, **kwargs):
|
||||
super().__init__(instance=instance, **kwargs)
|
||||
self.fields["groups"].initial = instance.group_set.all()
|
||||
|
||||
def save(self, commit: bool = True): # noqa FTB001
|
||||
instance = super().save(commit=False)
|
||||
if commit:
|
||||
instance.group_set.set(self.cleaned_data["groups"])
|
||||
return instance
|
||||
|
@ -17,6 +17,9 @@
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import ListView
|
||||
@ -25,6 +28,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from core.auth.mixins import CanEditMixin
|
||||
from core.models import Group, User
|
||||
from core.views import DetailFormView
|
||||
from core.views.forms import PermissionGroupsForm
|
||||
from core.views.widgets.select import AutoCompleteSelectMultipleUser
|
||||
|
||||
# Forms
|
||||
@ -130,3 +134,60 @@ class GroupDeleteView(CanEditMixin, DeleteView):
|
||||
pk_url_kwarg = "group_id"
|
||||
template_name = "core/delete_confirm.jinja"
|
||||
success_url = reverse_lazy("core:group_list")
|
||||
|
||||
|
||||
class PermissionGroupsUpdateView(PermissionRequiredMixin, UpdateView):
|
||||
"""Manage the groups that have a specific permission.
|
||||
|
||||
Notes:
|
||||
This is an `UpdateView`, but unlike typical `UpdateView`,
|
||||
it doesn't accept url arguments to retrieve the object
|
||||
to update.
|
||||
As such, a `PermissionGroupsUpdateView` can only deal with
|
||||
a single hardcoded permission.
|
||||
|
||||
This is not a limitation, but an on-purpose design,
|
||||
mainly for security matters.
|
||||
|
||||
Example:
|
||||
```python
|
||||
class AddSubscriptionGroupsView(PermissionGroupsUpdateView):
|
||||
permission = "subscription.add_subscription"
|
||||
success_url = reverse_lazy("foo:bar")
|
||||
```
|
||||
"""
|
||||
|
||||
permission_required = "auth.change_permission"
|
||||
template_name = "core/edit.jinja"
|
||||
form_class = PermissionGroupsForm
|
||||
permission = None
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if not self.permission:
|
||||
raise ImproperlyConfigured(
|
||||
f"{self.__class__.__name__} is missing the permission attribute. "
|
||||
"Please fill it with either a permission string "
|
||||
"or a Permission object."
|
||||
)
|
||||
if isinstance(self.permission, Permission):
|
||||
return self.permission
|
||||
if isinstance(self.permission, str):
|
||||
try:
|
||||
app_label, codename = self.permission.split(".")
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
"Permission name should be in the form "
|
||||
"app_label.permission_codename."
|
||||
) from e
|
||||
return get_object_or_404(
|
||||
Permission, codename=codename, content_type__app_label=app_label
|
||||
)
|
||||
raise TypeError(
|
||||
f"{self.__class__.__name__}.permission "
|
||||
f"must be a string or a permission instance."
|
||||
)
|
||||
|
||||
def get_success_url(self):
|
||||
# if children classes define a success url, return it,
|
||||
# else stay on the same page
|
||||
return self.success_url or self.request.path
|
||||
|
@ -228,3 +228,38 @@ Les groupes de ban existants sont les suivants :
|
||||
- `Banned from buying alcohol` : les utilisateurs interdits de vente d'alcool (non mineurs)
|
||||
- `Banned from counters` : les utilisateurs interdits d'utilisation des comptoirs
|
||||
- `Banned to subscribe` : les utilisateurs interdits de cotisation
|
||||
|
||||
## Groupes liés à une permission
|
||||
|
||||
Certaines actions sur le site demandent une permission en particulier,
|
||||
que l'on veut donner ou retirer n'importe quand.
|
||||
|
||||
Prenons par exemple les cotisations : lors de l'intégration,
|
||||
on veut permettre aux membres du bureau de l'Integ
|
||||
de créer des cotisations, et pareil pour les membres du bureau
|
||||
de la Welcome Week pendant cette dernière.
|
||||
|
||||
Dans ces cas-là, il est pertinent de mettre à disposition
|
||||
des administrateurs du site une page leur permettant
|
||||
de gérer quels groupes ont une permission donnée.
|
||||
Pour ce faire, il existe
|
||||
[PermissionGroupsUpdateView][core.views.PermissionGroupsUpdateView].
|
||||
|
||||
Pour l'utiliser, il suffit de créer une vue qui en hérite
|
||||
et de lui dire quelle est la permission dont on veut gérer
|
||||
les groupes :
|
||||
|
||||
```python
|
||||
from core.views.group import PermissionGroupsUpdateView
|
||||
|
||||
|
||||
class SubscriptionPermissionView(PermissionGroupsUpdateView):
|
||||
permission = "subscription.add_subscription"
|
||||
```
|
||||
|
||||
Configurez l'url de la vue, et c'est tout !
|
||||
La page ainsi générée contiendra un formulaire
|
||||
avec un unique champ permettant de sélectionner des groupes.
|
||||
Par défaut, seuls les utilisateurs avec la permission
|
||||
`auth.change_permission` auront accès à ce formulaire
|
||||
(donc, normalement, uniquement les utilisateurs Root).
|
||||
|
@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-19 18:12+0100\n"
|
||||
"POT-Creation-Date: 2025-02-12 15:32+0100\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -2914,7 +2914,7 @@ msgstr "Blouse"
|
||||
msgid "Not subscribed"
|
||||
msgstr "Non cotisant"
|
||||
|
||||
#: core/templates/core/user_detail.jinja
|
||||
#: core/templates/core/user_detail.jinja core/templates/core/user_tools.jinja
|
||||
#: subscription/templates/subscription/subscription.jinja
|
||||
msgid "New subscription"
|
||||
msgstr "Nouvelle cotisation"
|
||||
@ -3146,15 +3146,6 @@ msgstr "Supprimer les messages forum d'un utilisateur"
|
||||
msgid "Bans"
|
||||
msgstr "Bans"
|
||||
|
||||
#: core/templates/core/user_tools.jinja
|
||||
msgid "Subscriptions"
|
||||
msgstr "Cotisations"
|
||||
|
||||
#: core/templates/core/user_tools.jinja
|
||||
#: subscription/templates/subscription/stats.jinja
|
||||
msgid "Subscription stats"
|
||||
msgstr "Statistiques de cotisation"
|
||||
|
||||
#: core/templates/core/user_tools.jinja counter/forms.py
|
||||
#: counter/views/mixins.py
|
||||
msgid "Counters"
|
||||
@ -3227,6 +3218,19 @@ msgstr "Modérer les fichiers"
|
||||
msgid "Moderate pictures"
|
||||
msgstr "Modérer les photos"
|
||||
|
||||
#: core/templates/core/user_tools.jinja
|
||||
msgid "Subscriptions"
|
||||
msgstr "Cotisations"
|
||||
|
||||
#: core/templates/core/user_tools.jinja
|
||||
msgid "Manage permissions"
|
||||
msgstr "Gérer les permissions"
|
||||
|
||||
#: core/templates/core/user_tools.jinja
|
||||
#: subscription/templates/subscription/stats.jinja
|
||||
msgid "Subscription stats"
|
||||
msgstr "Statistiques de cotisation"
|
||||
|
||||
#: core/templates/core/user_tools.jinja pedagogy/templates/pedagogy/guide.jinja
|
||||
msgid "Create UV"
|
||||
msgstr "Créer UV"
|
||||
|
43
subscription/tests/test_permissions.py
Normal file
43
subscription/tests/test_permissions.py
Normal file
@ -0,0 +1,43 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from model_bakery import baker
|
||||
from pytest_django.asserts import assertRedirects
|
||||
|
||||
from club.models import Club, Membership
|
||||
from core.baker_recipes import subscriber_user
|
||||
from core.models import User
|
||||
|
||||
|
||||
class TestSubscriptionPermission(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user: User = subscriber_user.make()
|
||||
cls.admin = baker.make(User, is_superuser=True)
|
||||
cls.club = baker.make(Club)
|
||||
baker.make(Membership, user=cls.user, club=cls.club, role=7)
|
||||
|
||||
def test_give_permission(self):
|
||||
self.client.force_login(self.admin)
|
||||
response = self.client.post(
|
||||
reverse("subscription:perms"), {"groups": [self.club.board_group_id]}
|
||||
)
|
||||
assertRedirects(response, reverse("subscription:perms"))
|
||||
assert self.user.has_perm("subscription.add_subscription")
|
||||
|
||||
def test_remove_permission(self):
|
||||
self.client.force_login(self.admin)
|
||||
response = self.client.post(reverse("subscription:perms"), {"groups": []})
|
||||
assertRedirects(response, reverse("subscription:perms"))
|
||||
assert not self.user.has_perm("subscription.add_subscription")
|
||||
|
||||
def test_subscription_page_access(self):
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse("subscription:subscription"))
|
||||
assert response.status_code == 403
|
||||
|
||||
self.club.board_group.permissions.add(
|
||||
Permission.objects.get(codename="add_subscription")
|
||||
)
|
||||
response = self.client.get(reverse("subscription:subscription"))
|
||||
assert response.status_code == 200
|
@ -20,6 +20,7 @@ from subscription.views import (
|
||||
CreateSubscriptionNewUserFragment,
|
||||
NewSubscription,
|
||||
SubscriptionCreatedFragment,
|
||||
SubscriptionPermissionView,
|
||||
SubscriptionsStatsView,
|
||||
)
|
||||
|
||||
@ -41,5 +42,10 @@ urlpatterns = [
|
||||
SubscriptionCreatedFragment.as_view(),
|
||||
name="creation-success",
|
||||
),
|
||||
path(
|
||||
"perms/",
|
||||
SubscriptionPermissionView.as_view(),
|
||||
name="perms",
|
||||
),
|
||||
path("stats/", SubscriptionsStatsView.as_view(), name="stats"),
|
||||
]
|
||||
|
@ -21,6 +21,7 @@ from django.utils.timezone import localdate
|
||||
from django.views.generic import CreateView, DetailView, TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from core.views.group import PermissionGroupsUpdateView
|
||||
from counter.apps import PAYMENT_METHOD
|
||||
from subscription.forms import (
|
||||
SelectionDateForm,
|
||||
@ -77,6 +78,12 @@ class SubscriptionCreatedFragment(PermissionRequiredMixin, DetailView):
|
||||
context_object_name = "subscription"
|
||||
|
||||
|
||||
class SubscriptionPermissionView(PermissionGroupsUpdateView):
|
||||
"""Manage the groups that have access to the subscription creation page."""
|
||||
|
||||
permission = "subscription.add_subscription"
|
||||
|
||||
|
||||
class SubscriptionsStatsView(FormView):
|
||||
template_name = "subscription/stats.jinja"
|
||||
form_class = SelectionDateForm
|
||||
|
Loading…
x
Reference in New Issue
Block a user