create default club roles on club creation

This commit is contained in:
imperosol
2026-04-19 14:43:37 +02:00
parent 937f1e2535
commit 9b862cfefc
5 changed files with 153 additions and 3 deletions

View File

@@ -13,6 +13,8 @@
# #
# #
from django.contrib import admin from django.contrib import admin
from django.forms.models import ModelForm
from django.http import HttpRequest
from club.models import Club, ClubRole, Membership from club.models import Club, ClubRole, Membership
@@ -29,6 +31,17 @@ class ClubAdmin(admin.ModelAdmin):
"page", "page",
) )
def save_model(
self,
request: HttpRequest,
obj: Club,
form: ModelForm,
change: bool, # noqa: FBT001
):
super().save_model(request, obj, form, change)
if not change:
obj.create_default_roles()
@admin.register(ClubRole) @admin.register(ClubRole)
class ClubRoleAdmin(admin.ModelAdmin): class ClubRoleAdmin(admin.ModelAdmin):

View File

@@ -121,6 +121,26 @@ class Migration(migrations.Migration):
"verbose_name_plural": "club roles", "verbose_name_plural": "club roles",
}, },
), ),
migrations.AlterField(
model_name="club",
name="board_group",
field=models.OneToOneField(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="club_board",
to="core.group",
),
),
migrations.AlterField(
model_name="club",
name="members_group",
field=models.OneToOneField(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="club",
to="core.group",
),
),
migrations.AddConstraint( migrations.AddConstraint(
model_name="clubrole", model_name="clubrole",
constraint=models.CheckConstraint( constraint=models.CheckConstraint(

View File

@@ -28,7 +28,7 @@ from typing import Iterable, Self
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import RegexValidator, validate_email from django.core.validators import RegexValidator, validate_email
from django.db import models, transaction from django.db import ProgrammingError, models, transaction
from django.db.models import Exists, F, OuterRef, Q from django.db.models import Exists, F, OuterRef, Q
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
@@ -92,10 +92,10 @@ class Club(models.Model):
Page, related_name="club", blank=True, on_delete=models.PROTECT Page, related_name="club", blank=True, on_delete=models.PROTECT
) )
members_group = models.OneToOneField( members_group = models.OneToOneField(
Group, related_name="club", on_delete=models.PROTECT Group, related_name="club", on_delete=models.PROTECT, editable=False
) )
board_group = models.OneToOneField( board_group = models.OneToOneField(
Group, related_name="club_board", on_delete=models.PROTECT Group, related_name="club_board", on_delete=models.PROTECT, editable=False
) )
objects = ClubQuerySet.as_manager() objects = ClubQuerySet.as_manager()
@@ -183,6 +183,40 @@ class Club(models.Model):
self.page.parent = self.parent.page self.page.parent = self.parent.page
self.page.save(force_lock=True) self.page.save(force_lock=True)
def create_default_roles(self):
"""Create some roles that should exist by default for this club.
The created roles are : president, treasurer, active member and curious.
Warnings:
When calling this method, no club must exist yet for this club.
"""
if self.roles.exists():
raise ProgrammingError(
"Default roles can be created only for clubs "
"that don't have associated roles yet"
)
# The names are written in French, because there is no gettext involved
# for strings stored in database, and the majority of users are french.
roles = [
ClubRole(name="Président⸱e", is_board=True, is_presidency=True),
ClubRole(name="Trésorier⸱e", is_board=True, is_presidency=False),
ClubRole(name="Membre actif⸱ve", is_board=False, is_presidency=False),
ClubRole(
name="Curieux⸱euse",
description=(
"Les gens qui suivent l'activité "
"du club sans forcément y participer"
),
is_board=False,
is_presidency=False,
),
]
for i, role in enumerate(roles):
role.club = self
role.order = i
ClubRole.objects.bulk_create(roles)
def delete(self, *args, **kwargs) -> tuple[int, dict[str, int]]: def delete(self, *args, **kwargs) -> tuple[int, dict[str, int]]:
self.board_group.delete() self.board_group.delete()
self.members_group.delete() self.members_group.delete()

View File

@@ -1,11 +1,14 @@
from datetime import timedelta from datetime import timedelta
import pytest import pytest
from django.conf import settings
from django.db import ProgrammingError
from django.test import Client from django.test import Client
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import localdate from django.utils.timezone import localdate
from model_bakery import baker from model_bakery import baker
from model_bakery.recipe import Recipe from model_bakery.recipe import Recipe
from pytest_django.asserts import assertRedirects
from club.models import Club, ClubRole, Membership from club.models import Club, ClubRole, Membership
from core.baker_recipes import subscriber_user from core.baker_recipes import subscriber_user
@@ -47,3 +50,78 @@ def test_club_list(client: Client, nb_additional_clubs: int, is_fragment):
headers = {"HX-Request": True} if is_fragment else {} headers = {"HX-Request": True} if is_fragment else {}
res = client.get(reverse("club:club_list"), headers=headers) res = client.get(reverse("club:club_list"), headers=headers)
assert res.status_code == 200 assert res.status_code == 200
def assert_club_created(club_name: str):
club = Club.objects.last()
assert club.name == club_name
assert club.board_group.name == f"{club_name} - Bureau"
assert club.members_group.name == f"{club_name} - Membres"
# default roles should be added on club creation,
# whether the creation happens on the admin site or on the user site
assert list(club.roles.values("name", "is_presidency", "is_board")) == [
{"name": "Président⸱e", "is_presidency": True, "is_board": True},
{"name": "Trésorier⸱e", "is_presidency": False, "is_board": True},
{"name": "Membre actif⸱ve", "is_presidency": False, "is_board": False},
{"name": "Curieux⸱euse", "is_presidency": False, "is_board": False},
]
@pytest.mark.django_db
def test_create_view(admin_client: Client):
"""Test that the club creation view works well"""
res = admin_client.get(reverse("club:club_new"))
assert res.status_code == 200
res = admin_client.post(
reverse("club:club_new"),
data={"name": "foo", "parent": settings.SITH_MAIN_CLUB_ID},
)
club = Club.objects.last()
assertRedirects(res, club.get_absolute_url())
assert_club_created("foo")
@pytest.mark.django_db
def test_default_roles_for_club_with_roles_fails():
"""Test that an Error is raised if trying to create
default roles for a club that already has roles.
"""
club = baker.make(Club)
baker.make(ClubRole, club=club)
with pytest.raises(ProgrammingError):
club.create_default_roles()
@pytest.mark.django_db
class TestAdminInterface:
def test_create(self, admin_client: Client):
"""Test the creation of a club via the admin interface."""
res = admin_client.post(
reverse("admin:club_club_add"),
data={
"name": "foo",
"parent": settings.SITH_MAIN_CLUB_ID,
"address": "Rome",
},
)
assertRedirects(res, reverse("admin:club_club_changelist"))
assert_club_created("foo")
def test_change(self, admin_client: Client):
"""Test the edition of a club via the admin interface."""
club = baker.make(Club)
res = admin_client.post(
reverse("admin:club_club_change", kwargs={"object_id": club.id}),
data={
"name": "foo",
"page": club.page_id,
"home": club.home_id,
"address": club.address,
},
)
assertRedirects(res, reverse("admin:club_club_changelist"))
club.refresh_from_db()
assert club.name == "foo"
# Club roles shouldn't be modified when editing the club on the admin interface
# This club had no roles beforehand, therefore it shouldn't have roles now.
assert not club.roles.exists()

View File

@@ -580,6 +580,11 @@ class ClubCreateView(PermissionRequiredMixin, CreateView):
template_name = "core/create.jinja" template_name = "core/create.jinja"
permission_required = "club.add_club" permission_required = "club.add_club"
def form_valid(self, form):
res = super().form_valid(form)
self.object.create_default_roles()
return res
class MembershipSetOldView(CanEditMixin, SingleObjectMixin, View): class MembershipSetOldView(CanEditMixin, SingleObjectMixin, View):
"""Set a membership as being old.""" """Set a membership as being old."""