From 09c41140bbb998ebaffe2422317578f9e11be912 Mon Sep 17 00:00:00 2001 From: imperosol Date: Wed, 15 Apr 2026 22:47:28 +0200 Subject: [PATCH] feat: page to create club roles --- club/forms.py | 28 +++++++++- club/templates/club/club_roles.jinja | 27 ++++++++- club/urls.py | 18 ++++++ club/views.py | 84 +++++++++++++++++++++++++++- core/templates/core/create.jinja | 11 +++- 5 files changed, 159 insertions(+), 9 deletions(-) diff --git a/club/forms.py b/club/forms.py index 4e5797e2..3ab9efd9 100644 --- a/club/forms.py +++ b/club/forms.py @@ -321,9 +321,6 @@ class ClubRoleForm(forms.ModelForm): "is_active": forms.CheckboxInput(attrs={"class": "switch"}), } - def __init__(self, *args, label_suffix="", **kwargs): - super().__init__(*args, label_suffix=label_suffix, **kwargs) - def clean(self): cleaned_data = super().clean() if "ORDER" in cleaned_data: @@ -331,6 +328,31 @@ class ClubRoleForm(forms.ModelForm): return cleaned_data +class ClubRoleCreateForm(forms.ModelForm): + """Form to create a club role. + + Notes: + For UX purposes, users are not meant to fill `is_presidency` + and `is_board`, so those values are required by the form constructor + in order to initialize the instance properly. + """ + + error_css_class = "error" + required_css_class = "required" + + class Meta: + model = ClubRole + fields = ["name", "description"] + + def __init__( + self, *args, club: Club, is_presidency: bool, is_board: bool, **kwargs + ): + super().__init__(*args, **kwargs) + self.instance.club = club + self.instance.is_presidency = is_presidency + self.instance.is_board = is_board + + class ClubRoleBaseFormSet(forms.BaseInlineFormSet): ordering_widget = forms.HiddenInput() diff --git a/club/templates/club/club_roles.jinja b/club/templates/club/club_roles.jinja index ab9310ba..b008419a 100644 --- a/club/templates/club/club_roles.jinja +++ b/club/templates/club/club_roles.jinja @@ -54,7 +54,14 @@ {{ form.management_form }} {{ form.non_form_errors() }}

{% trans %}Presidency{% endtrans %}

-
+ + {% trans %}add role{% endtrans %} + +
{% for subform in form %} {% if subform.is_presidency.value() %} {{ display_subform(subform) }} @@ -62,7 +69,14 @@ {% endfor %}

{% trans %}Board{% endtrans %}

-
+ + {% trans %}add role{% endtrans %} + +
{% for subform in form %} {% if subform.is_board.value() and not subform.is_presidency.value() %} {{ display_subform(subform) }} @@ -70,7 +84,14 @@ {% endfor %}

{% trans %}Members{% endtrans %}

-
+ + {% trans %}add role{% endtrans %} + +
{% for subform in form %} {% if not subform.is_board.value() %} {{ display_subform(subform) }} diff --git a/club/urls.py b/club/urls.py index 7a9bc109..6a08bbe5 100644 --- a/club/urls.py +++ b/club/urls.py @@ -35,6 +35,9 @@ from club.views import ( ClubPageEditView, ClubPageHistView, ClubRevView, + ClubRoleBoardCreateView, + ClubRoleMemberCreateView, + ClubRolePresidencyCreateView, ClubRoleUpdateView, ClubSellingCSVView, ClubSellingView, @@ -73,6 +76,21 @@ urlpatterns = [ name="club_old_members", ), path("/role/", ClubRoleUpdateView.as_view(), name="club_roles"), + path( + "/role/new/president/", + ClubRolePresidencyCreateView.as_view(), + name="new_role_president", + ), + path( + "/role/new/board/", + ClubRoleBoardCreateView.as_view(), + name="new_role_board", + ), + path( + "/role/new/member/", + ClubRoleMemberCreateView.as_view(), + name="new_role_member", + ), path("/sellings/", ClubSellingView.as_view(), name="club_sellings"), path( "/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv" diff --git a/club/views.py b/club/views.py index 478851f2..cd029730 100644 --- a/club/views.py +++ b/club/views.py @@ -54,12 +54,13 @@ from club.forms import ( ClubAdminEditForm, ClubEditForm, ClubOldMemberForm, + ClubRoleCreateForm, ClubRoleFormSet, JoinClubForm, MailingForm, SellingsForm, ) -from club.models import Club, Mailing, MailingSubscription, Membership +from club.models import Club, ClubRole, Mailing, MailingSubscription, Membership from com.models import Poster from com.views import ( PosterCreateBaseView, @@ -400,10 +401,91 @@ class ClubRoleUpdateView( user=self.request.user, role__is_presidency=True ).exists() + def get_form_kwargs(self): + return super().get_form_kwargs() | {"form_kwargs": {"label_suffix": ""}} + def get_success_url(self): return self.request.path +class ClubRoleBaseCreateView(UserPassesTestMixin, SuccessMessageMixin, CreateView): + """View to create a new Club Role, using [][club.forms.ClubRoleCreateForm]. + + This view isn't meant to be called directly, but rather subclassed for each + type of role that can exist : + + - `[ClubRolePresidencyCreateView][club.views.ClubRolePresidencyCreateView]` + to create a presidency role + - `[ClubRoleBoardCreateView][club.views.ClubRoleBoardCreateView]` + to create a board role + - `[ClubRoleMemberCreateView][club.views.ClubRoleMemberCreateView]` + to create a member role + + Each subclass have to override the following variables : + + - `is_presidency` and `is_board`, indicating what type of role + the view creates. + - `role_description`, which is the title of the page, indication + the user what kind of role is being created. + + This way, we are making sure the correct type of role will + be created, without bothering the user with the implementation details. + """ + + form_class = ClubRoleCreateForm + model = ClubRole + template_name = "core/create.jinja" + success_message = _("Role %(name)s created") + role_description = "" + is_presidency: bool + is_board: bool + + @cached_property + def club(self): + return get_object_or_404(Club, id=self.kwargs["club_id"]) + + def test_func(self): + return self.request.user.is_authenticated and ( + self.request.user.has_perm("club.add_clubrole") + or self.club.members.filter( + user=self.request.user, role__is_presidency=True + ).exists() + ) + + def get_form_kwargs(self): + return super().get_form_kwargs() | { + "club": self.club, + "is_presidency": self.is_presidency, + "is_board": self.is_board, + } + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) | { + "object_name": self.role_description + } + + def get_success_url(self): + return reverse("club:club_roles", kwargs={"club_id": self.club.id}) + + +class ClubRolePresidencyCreateView(ClubRoleBaseCreateView): + is_presidency = True + is_board = True + role_description = _("club role \u2013 presidency") + + +class ClubRoleBoardCreateView(ClubRoleBaseCreateView): + is_presidency = False + is_board = True + role_description = _("club role \u2013 board") + + +class ClubRoleMemberCreateView(ClubRoleBaseCreateView): + is_presidency = False + is_board = False + role_description = _("club role \u2013 member") + + class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView): """Sales of a club.""" diff --git a/core/templates/core/create.jinja b/core/templates/core/create.jinja index 332bb94b..724d2bc3 100644 --- a/core/templates/core/create.jinja +++ b/core/templates/core/create.jinja @@ -1,11 +1,18 @@ {% extends "core/base.jinja" %} +{# if the template context has the `object_name` variable, + then this one will be used in the page title, + instead of the result of `str(object)` #} +{% if not object_name %} + {% set object_name=form.instance.__class__._meta.verbose_name %} +{% endif %} + {% block title %} - {% trans name=form.instance.__class__._meta.verbose_name %}Create {{ name }}{% endtrans %} + {% trans name=object_name %}Create {{ name }}{% endtrans %} {% endblock %} {% block content %} -

{% trans name=form.instance.__class__._meta.verbose_name %}Create {{ name }}{% endtrans %}

+

{% trans name=object_name %}Create {{ name }}{% endtrans %}

{% csrf_token %} {{ form.as_p() }}