feat: page to create club roles

This commit is contained in:
imperosol
2026-04-15 22:47:28 +02:00
parent d31b7ad32d
commit 09c41140bb
5 changed files with 159 additions and 9 deletions

View File

@@ -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()

View File

@@ -54,7 +54,14 @@
{{ form.management_form }}
{{ form.non_form_errors() }}
<h3>{% trans %}Presidency{% endtrans %}</h3>
<div x-sort="reorder($item, { isBoard: true, isPresidency: true })" x-sort:group="roles">
<a class="btn btn-blue" href="{{ url("club:new_role_president", club_id=club.id) }}">
<i class="fa fa-plus"></i> {% trans %}add role{% endtrans %}
</a>
<div
x-sort="reorder($item, { isBoard: true, isPresidency: true })"
x-sort:group="roles"
x-ref="presidencyFormSet"
>
{% for subform in form %}
{% if subform.is_presidency.value() %}
{{ display_subform(subform) }}
@@ -62,7 +69,14 @@
{% endfor %}
</div>
<h3>{% trans %}Board{% endtrans %}</h3>
<div x-sort="reorder($item, { isBoard: true, isPresidency: false })" x-sort:group="roles">
<a class="btn btn-blue" href="{{ url("club:new_role_board", club_id=club.id) }}">
<i class="fa fa-plus"></i> {% trans %}add role{% endtrans %}
</a>
<div
x-sort="reorder($item, { isBoard: true, isPresidency: false })"
x-sort:group="roles"
x-ref="boardFormSet"
>
{% for subform in form %}
{% if subform.is_board.value() and not subform.is_presidency.value() %}
{{ display_subform(subform) }}
@@ -70,7 +84,14 @@
{% endfor %}
</div>
<h3>{% trans %}Members{% endtrans %}</h3>
<div x-sort="reorder($item, { isBoard: false, isPresidency: false })" x-sort:group="roles">
<a class="btn btn-blue" href="{{ url("club:new_role_member", club_id=club.id) }}">
<i class="fa fa-plus"></i> {% trans %}add role{% endtrans %}
</a>
<div
x-sort="reorder($item, { isBoard: false, isPresidency: false })"
x-sort:group="roles"
x-ref="memberFormSet"
>
{% for subform in form %}
{% if not subform.is_board.value() %}
{{ display_subform(subform) }}

View File

@@ -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("<int:club_id>/role/", ClubRoleUpdateView.as_view(), name="club_roles"),
path(
"<int:club_id>/role/new/president/",
ClubRolePresidencyCreateView.as_view(),
name="new_role_president",
),
path(
"<int:club_id>/role/new/board/",
ClubRoleBoardCreateView.as_view(),
name="new_role_board",
),
path(
"<int:club_id>/role/new/member/",
ClubRoleMemberCreateView.as_view(),
name="new_role_member",
),
path("<int:club_id>/sellings/", ClubSellingView.as_view(), name="club_sellings"),
path(
"<int:club_id>/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv"

View File

@@ -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."""