mirror of
https://github.com/ae-utbm/sith.git
synced 2025-02-21 23:17:14 +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>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div>
|
<div>
|
||||||
{% if user.is_root or user.is_board_member %}
|
{% 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">
|
<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") %}
|
{% if user.has_perm("core.view_userban") %}
|
||||||
<li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li>
|
<li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li>
|
||||||
{% endif %}
|
{% 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 %}
|
{% 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>
|
<li><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@ -163,6 +159,35 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if user.memberships.filter(end_date=None).all().count() > 0 %}
|
||||||
<div>
|
<div>
|
||||||
<h4>{% trans %}Club tools{% endtrans %}</h4>
|
<h4>{% trans %}Club tools{% endtrans %}</h4>
|
||||||
|
@ -28,6 +28,7 @@ from captcha.fields import CaptchaField
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.staticfiles.management.commands.collectstatic import (
|
from django.contrib.staticfiles.management.commands.collectstatic import (
|
||||||
staticfiles_storage,
|
staticfiles_storage,
|
||||||
)
|
)
|
||||||
@ -440,3 +441,25 @@ class GiftForm(forms.ModelForm):
|
|||||||
id=user_id
|
id=user_id
|
||||||
)
|
)
|
||||||
self.fields["user"].widget = forms.HiddenInput()
|
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 import forms
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
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.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import ListView
|
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.auth.mixins import CanEditMixin
|
||||||
from core.models import Group, User
|
from core.models import Group, User
|
||||||
from core.views import DetailFormView
|
from core.views import DetailFormView
|
||||||
|
from core.views.forms import PermissionGroupsForm
|
||||||
from core.views.widgets.select import AutoCompleteSelectMultipleUser
|
from core.views.widgets.select import AutoCompleteSelectMultipleUser
|
||||||
|
|
||||||
# Forms
|
# Forms
|
||||||
@ -130,3 +134,60 @@ class GroupDeleteView(CanEditMixin, DeleteView):
|
|||||||
pk_url_kwarg = "group_id"
|
pk_url_kwarg = "group_id"
|
||||||
template_name = "core/delete_confirm.jinja"
|
template_name = "core/delete_confirm.jinja"
|
||||||
success_url = reverse_lazy("core:group_list")
|
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 buying alcohol` : les utilisateurs interdits de vente d'alcool (non mineurs)
|
||||||
- `Banned from counters` : les utilisateurs interdits d'utilisation des comptoirs
|
- `Banned from counters` : les utilisateurs interdits d'utilisation des comptoirs
|
||||||
- `Banned to subscribe` : les utilisateurs interdits de cotisation
|
- `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 ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"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"
|
"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"
|
||||||
@ -2914,7 +2914,7 @@ msgstr "Blouse"
|
|||||||
msgid "Not subscribed"
|
msgid "Not subscribed"
|
||||||
msgstr "Non cotisant"
|
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
|
#: subscription/templates/subscription/subscription.jinja
|
||||||
msgid "New subscription"
|
msgid "New subscription"
|
||||||
msgstr "Nouvelle cotisation"
|
msgstr "Nouvelle cotisation"
|
||||||
@ -3146,15 +3146,6 @@ msgstr "Supprimer les messages forum d'un utilisateur"
|
|||||||
msgid "Bans"
|
msgid "Bans"
|
||||||
msgstr "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
|
#: core/templates/core/user_tools.jinja counter/forms.py
|
||||||
#: counter/views/mixins.py
|
#: counter/views/mixins.py
|
||||||
msgid "Counters"
|
msgid "Counters"
|
||||||
@ -3227,6 +3218,19 @@ msgstr "Modérer les fichiers"
|
|||||||
msgid "Moderate pictures"
|
msgid "Moderate pictures"
|
||||||
msgstr "Modérer les photos"
|
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
|
#: core/templates/core/user_tools.jinja pedagogy/templates/pedagogy/guide.jinja
|
||||||
msgid "Create UV"
|
msgid "Create UV"
|
||||||
msgstr "Créer 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,
|
CreateSubscriptionNewUserFragment,
|
||||||
NewSubscription,
|
NewSubscription,
|
||||||
SubscriptionCreatedFragment,
|
SubscriptionCreatedFragment,
|
||||||
|
SubscriptionPermissionView,
|
||||||
SubscriptionsStatsView,
|
SubscriptionsStatsView,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,5 +42,10 @@ urlpatterns = [
|
|||||||
SubscriptionCreatedFragment.as_view(),
|
SubscriptionCreatedFragment.as_view(),
|
||||||
name="creation-success",
|
name="creation-success",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"perms/",
|
||||||
|
SubscriptionPermissionView.as_view(),
|
||||||
|
name="perms",
|
||||||
|
),
|
||||||
path("stats/", SubscriptionsStatsView.as_view(), name="stats"),
|
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 import CreateView, DetailView, TemplateView
|
||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
|
|
||||||
|
from core.views.group import PermissionGroupsUpdateView
|
||||||
from counter.apps import PAYMENT_METHOD
|
from counter.apps import PAYMENT_METHOD
|
||||||
from subscription.forms import (
|
from subscription.forms import (
|
||||||
SelectionDateForm,
|
SelectionDateForm,
|
||||||
@ -77,6 +78,12 @@ class SubscriptionCreatedFragment(PermissionRequiredMixin, DetailView):
|
|||||||
context_object_name = "subscription"
|
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):
|
class SubscriptionsStatsView(FormView):
|
||||||
template_name = "subscription/stats.jinja"
|
template_name = "subscription/stats.jinja"
|
||||||
form_class = SelectionDateForm
|
form_class = SelectionDateForm
|
||||||
|
Loading…
x
Reference in New Issue
Block a user