mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-22 11:35:06 +00:00
adapt club members pages to new club roles framework
This commit is contained in:
@@ -23,13 +23,12 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.db.models import Exists, OuterRef, Q, QuerySet
|
||||||
from django.db.models import Exists, OuterRef, Q
|
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from club.models import Club, Mailing, MailingSubscription, Membership
|
from club.models import Club, ClubRole, Mailing, MailingSubscription, Membership
|
||||||
from core.models import User
|
from core.models import User
|
||||||
from core.views.forms import SelectDateTime
|
from core.views.forms import SelectDateTime
|
||||||
from core.views.widgets.ajax_select import (
|
from core.views.widgets.ajax_select import (
|
||||||
@@ -215,9 +214,7 @@ class ClubOldMemberForm(forms.Form):
|
|||||||
|
|
||||||
def __init__(self, *args, user: User, club: Club, **kwargs):
|
def __init__(self, *args, user: User, club: Club, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["members_old"].queryset = (
|
self.fields["members_old"].queryset = club.members.ongoing().editable_by(user)
|
||||||
Membership.objects.ongoing().filter(club=club).editable_by(user)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ClubMemberForm(forms.ModelForm):
|
class ClubMemberForm(forms.ModelForm):
|
||||||
@@ -235,19 +232,14 @@ class ClubMemberForm(forms.ModelForm):
|
|||||||
self.request_user = request_user
|
self.request_user = request_user
|
||||||
self.request_user_membership = self.club.get_membership_for(self.request_user)
|
self.request_user_membership = self.club.get_membership_for(self.request_user)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["role"].required = True
|
self.fields["role"].queryset = self.available_roles
|
||||||
self.fields["role"].choices = [
|
|
||||||
(value, name)
|
|
||||||
for value, name in settings.SITH_CLUB_ROLES.items()
|
|
||||||
if value <= self.max_available_role
|
|
||||||
]
|
|
||||||
self.instance.club = club
|
self.instance.club = club
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_available_role(self):
|
def available_roles(self) -> QuerySet[ClubRole]:
|
||||||
"""The greatest role that will be obtainable with this form."""
|
"""The greatest role that will be obtainable with this form."""
|
||||||
# this is unreachable, because it will be overridden by subclasses
|
# this is unreachable, because it will be overridden by subclasses
|
||||||
return -1 # pragma: no cover
|
return ClubRole.objects.none() # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
class ClubAddMemberForm(ClubMemberForm):
|
class ClubAddMemberForm(ClubMemberForm):
|
||||||
@@ -258,7 +250,7 @@ class ClubAddMemberForm(ClubMemberForm):
|
|||||||
widgets = {"user": AutoCompleteSelectUser}
|
widgets = {"user": AutoCompleteSelectUser}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def max_available_role(self):
|
def available_roles(self):
|
||||||
"""The greatest role that will be obtainable with this form.
|
"""The greatest role that will be obtainable with this form.
|
||||||
|
|
||||||
Admins and the club president can attribute any role.
|
Admins and the club president can attribute any role.
|
||||||
@@ -266,13 +258,13 @@ class ClubAddMemberForm(ClubMemberForm):
|
|||||||
Other users cannot attribute roles with this form
|
Other users cannot attribute roles with this form
|
||||||
"""
|
"""
|
||||||
if self.request_user.has_perm("club.add_membership"):
|
if self.request_user.has_perm("club.add_membership"):
|
||||||
return settings.SITH_CLUB_ROLES_ID["President"]
|
return self.club.roles.all()
|
||||||
membership = self.request_user_membership
|
membership = self.request_user_membership
|
||||||
if membership is None or membership.role <= settings.SITH_MAXIMUM_FREE_ROLE:
|
if membership is None or not membership.role.is_board:
|
||||||
return -1
|
return ClubRole.objects.none()
|
||||||
if membership.role == settings.SITH_CLUB_ROLES_ID["President"]:
|
if membership.role.is_presidency:
|
||||||
return membership.role
|
return self.club.roles.all()
|
||||||
return membership.role - 1
|
return self.club.roles.above_instance(membership.role)
|
||||||
|
|
||||||
def clean_user(self):
|
def clean_user(self):
|
||||||
"""Check that the user is not trying to add a user already in the club.
|
"""Check that the user is not trying to add a user already in the club.
|
||||||
@@ -296,13 +288,11 @@ class JoinClubForm(ClubMemberForm):
|
|||||||
|
|
||||||
def __init__(self, *args, club: Club, request_user: User, **kwargs):
|
def __init__(self, *args, club: Club, request_user: User, **kwargs):
|
||||||
super().__init__(*args, club=club, request_user=request_user, **kwargs)
|
super().__init__(*args, club=club, request_user=request_user, **kwargs)
|
||||||
# this form doesn't manage the user who will join the club,
|
|
||||||
# so we must set this here to avoid errors
|
|
||||||
self.instance.user = self.request_user
|
self.instance.user = self.request_user
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def max_available_role(self):
|
def available_roles(self):
|
||||||
return settings.SITH_MAXIMUM_FREE_ROLE
|
return self.club.roles.filter(is_board=False)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Check that the user is subscribed and isn't already in the club."""
|
"""Check that the user is subscribed and isn't already in the club."""
|
||||||
|
|||||||
@@ -5,24 +5,33 @@ from django.conf import settings
|
|||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from django.db.migrations.state import StateApps
|
from django.db.migrations.state import StateApps
|
||||||
from django.db.models import Case, When
|
from django.db.models import Case, When
|
||||||
from django.utils import translation
|
|
||||||
|
PRESIDENT_ROLE = 10
|
||||||
|
SITH_CLUB_ROLES = {
|
||||||
|
10: "Président⸱e",
|
||||||
|
9: "Vice-Président⸱e",
|
||||||
|
7: "Trésorier⸱e",
|
||||||
|
5: "Responsable communication",
|
||||||
|
4: "Secrétaire",
|
||||||
|
3: "Responsable info",
|
||||||
|
2: "Membre du bureau",
|
||||||
|
1: "Membre actif⸱ve",
|
||||||
|
0: "Curieux⸱euse",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def migrate_roles(apps: StateApps, schema_editor):
|
def migrate_roles(apps: StateApps, schema_editor):
|
||||||
ClubRole = apps.get_model("club", "ClubRole")
|
ClubRole = apps.get_model("club", "ClubRole")
|
||||||
Membership = apps.get_model("club", "Membership")
|
Membership = apps.get_model("club", "Membership")
|
||||||
|
|
||||||
translation.activate("fr")
|
|
||||||
|
|
||||||
updates = []
|
updates = []
|
||||||
presidency = settings.SITH_CLUB_ROLES_ID["President"]
|
|
||||||
for club_id, role in Membership.objects.values_list("club", "role").distinct():
|
for club_id, role in Membership.objects.values_list("club", "role").distinct():
|
||||||
new_role = ClubRole.objects.create(
|
new_role = ClubRole.objects.create(
|
||||||
name=SITH_CLUB_ROLES[role],
|
name=SITH_CLUB_ROLES[role],
|
||||||
is_board=role > settings.SITH_MAXIMUM_FREE_ROLE,
|
is_board=role > settings.SITH_MAXIMUM_FREE_ROLE,
|
||||||
is_presidency=role == presidency,
|
is_presidency=role == PRESIDENT_ROLE,
|
||||||
club_id=club_id,
|
club_id=club_id,
|
||||||
order=presidency - role,
|
order=PRESIDENT_ROLE - role,
|
||||||
)
|
)
|
||||||
updates.append(When(role=role, then=new_role.id))
|
updates.append(When(role=role, then=new_role.id))
|
||||||
# all updates must happen at the same time
|
# all updates must happen at the same time
|
||||||
|
|||||||
@@ -138,9 +138,7 @@ class Club(models.Model):
|
|||||||
@cached_property
|
@cached_property
|
||||||
def president(self) -> Membership | None:
|
def president(self) -> Membership | None:
|
||||||
"""Fetch the membership of the current president of this club."""
|
"""Fetch the membership of the current president of this club."""
|
||||||
return self.members.filter(
|
return self.members.filter(end_date=None).order_by("role__order").first()
|
||||||
role=settings.SITH_CLUB_ROLES_ID["President"], end_date=None
|
|
||||||
).first()
|
|
||||||
|
|
||||||
def check_loop(self):
|
def check_loop(self):
|
||||||
"""Raise a validation error when a loop is found within the parent list."""
|
"""Raise a validation error when a loop is found within the parent list."""
|
||||||
@@ -208,7 +206,9 @@ class Club(models.Model):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def current_members(self) -> list[Membership]:
|
def current_members(self) -> list[Membership]:
|
||||||
return list(self.members.ongoing().select_related("user").order_by("-role"))
|
return list(
|
||||||
|
self.members.ongoing().select_related("user", "role").order_by("-role")
|
||||||
|
)
|
||||||
|
|
||||||
def get_membership_for(self, user: User) -> Membership | None:
|
def get_membership_for(self, user: User) -> Membership | None:
|
||||||
"""Return the current membership of the given user."""
|
"""Return the current membership of the given user."""
|
||||||
@@ -256,7 +256,7 @@ class ClubRole(OrderedModel):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} - {self.club.name}"
|
return self.name
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
return f"{self.name} - {self.club.name}"
|
return f"{self.name} - {self.club.name}"
|
||||||
@@ -265,14 +265,29 @@ class ClubRole(OrderedModel):
|
|||||||
return reverse("club:club_roles", kwargs={"club_id": self.club_id})
|
return reverse("club:club_roles", kwargs={"club_id": self.club_id})
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
errors = []
|
||||||
if self.is_presidency and not self.is_board:
|
if self.is_presidency and not self.is_board:
|
||||||
raise ValidationError(
|
errors.append(
|
||||||
|
ValidationError(
|
||||||
_(
|
_(
|
||||||
"Role %(name)s was declared as a presidency role "
|
"Role %(name)s was declared as a presidency role "
|
||||||
"without being a board role"
|
"without being a board role"
|
||||||
)
|
)
|
||||||
% {"name": self.name}
|
% {"name": self.name}
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
self.is_board
|
||||||
|
and self.club.roles.filter(is_board=False, order__lt=self.order).exists()
|
||||||
|
):
|
||||||
|
errors.append(
|
||||||
|
ValidationError(
|
||||||
|
_("Board role %(role)s cannot be placed below a member role")
|
||||||
|
% {"role": self.name}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise ValidationError(errors)
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
|
|
||||||
@@ -321,7 +336,7 @@ class MembershipQuerySet(models.QuerySet):
|
|||||||
user=user,
|
user=user,
|
||||||
club=OuterRef("club"),
|
club=OuterRef("club"),
|
||||||
role__is_board=True,
|
role__is_board=True,
|
||||||
role__order__gt=OuterRef("role__order"),
|
role__order__lt=OuterRef("role__order"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
{% for m in members %}
|
{% for m in members %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user_profile_link(m.user) }}</td>
|
<td>{{ user_profile_link(m.user) }}</td>
|
||||||
<td>{{ settings.SITH_CLUB_ROLES[m.role] }}</td>
|
<td>{{ m.role.name }}</td>
|
||||||
<td>{{ m.description }}</td>
|
<td>{{ m.description }}</td>
|
||||||
<td>{{ m.start_date }}</td>
|
<td>{{ m.start_date }}</td>
|
||||||
{%- if can_end_membership -%}
|
{%- if can_end_membership -%}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
{% for member in old_members %}
|
{% for member in old_members %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user_profile_link(member.user) }}</td>
|
<td>{{ user_profile_link(member.user) }}</td>
|
||||||
<td>{{ settings.SITH_CLUB_ROLES[member.role] }}</td>
|
<td>{{ member.role.name }}</td>
|
||||||
<td>{{ member.description }}</td>
|
<td>{{ member.description }}</td>
|
||||||
<td>{{ member.start_date }}</td>
|
<td>{{ member.start_date }}</td>
|
||||||
<td>{{ member.end_date }}</td>
|
<td>{{ member.end_date }}</td>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from model_bakery import baker
|
|||||||
from model_bakery.recipe import Recipe
|
from model_bakery.recipe import Recipe
|
||||||
from pytest_django.asserts import assertNumQueries
|
from pytest_django.asserts import assertNumQueries
|
||||||
|
|
||||||
from club.models import Club, Membership, ClubRole
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
from core.models import Group, Page, User
|
from core.models import Group, Page, User
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import csv
|
|||||||
import itertools
|
import itertools
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
|
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
|
||||||
@@ -318,7 +317,7 @@ class ClubMembersView(
|
|||||||
membership = self.object.get_membership_for(self.request.user)
|
membership = self.object.get_membership_for(self.request.user)
|
||||||
if (
|
if (
|
||||||
membership
|
membership
|
||||||
and membership.role <= settings.SITH_MAXIMUM_FREE_ROLE
|
and not membership.role.is_board
|
||||||
and not self.request.user.has_perm("club.add_membership")
|
and not self.request.user.has_perm("club.add_membership")
|
||||||
):
|
):
|
||||||
# Simple club members won't see the form anymore.
|
# Simple club members won't see the form anymore.
|
||||||
@@ -343,8 +342,8 @@ class ClubMembersView(
|
|||||||
kwargs["members"] = list(
|
kwargs["members"] = list(
|
||||||
self.object.members.ongoing()
|
self.object.members.ongoing()
|
||||||
.annotate(is_editable=Q(id__in=editable))
|
.annotate(is_editable=Q(id__in=editable))
|
||||||
.order_by("-role")
|
.order_by("role__order")
|
||||||
.select_related("user")
|
.select_related("user", "role")
|
||||||
)
|
)
|
||||||
kwargs["can_end_membership"] = len(editable) > 0
|
kwargs["can_end_membership"] = len(editable) > 0
|
||||||
return kwargs
|
return kwargs
|
||||||
@@ -372,8 +371,8 @@ class ClubOldMembersView(ClubTabsMixin, PermissionRequiredMixin, DetailView):
|
|||||||
return super().get_context_data(**kwargs) | {
|
return super().get_context_data(**kwargs) | {
|
||||||
"old_members": (
|
"old_members": (
|
||||||
self.object.members.exclude(end_date=None)
|
self.object.members.exclude(end_date=None)
|
||||||
.order_by("-role", "description", "-end_date")
|
.order_by("role__order", "description", "-end_date")
|
||||||
.select_related("user")
|
.select_related("user", "role")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,9 +723,7 @@ class MailingAutoGenerationView(View):
|
|||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
club = self.mailing.club
|
club = self.mailing.club
|
||||||
self.mailing.subscriptions.all().delete()
|
self.mailing.subscriptions.all().delete()
|
||||||
members = club.members.filter(
|
members = club.members.ongoing().filter(role__is_board=True)
|
||||||
role__gte=settings.SITH_CLUB_ROLES_ID["Board member"]
|
|
||||||
).exclude(end_date__lte=timezone.now())
|
|
||||||
for member in members.all():
|
for member in members.all():
|
||||||
MailingSubscription(user=member.user, mailing=self.mailing).save()
|
MailingSubscription(user=member.user, mailing=self.mailing).save()
|
||||||
return redirect("club:mailing", club_id=club.id)
|
return redirect("club:mailing", club_id=club.id)
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in profile.memberships.filter(end_date=None).all() %}
|
{% for m in profile.memberships.ongoing().select_related("role") %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url('club:club_members', club_id=m.club.id) }}">{{ m.club }}</a></td>
|
<td><a href="{{ url('club:club_members', club_id=m.club.id) }}">{{ m.club }}</a></td>
|
||||||
<td>{{ settings.SITH_CLUB_ROLES[m.role] }}</td>
|
<td>{{ m.role.name }}</td>
|
||||||
<td>{{ m.description }}</td>
|
<td>{{ m.description }}</td>
|
||||||
<td>{{ m.start_date }}</td>
|
<td>{{ m.start_date }}</td>
|
||||||
{% if m.can_be_edited_by(user) %}
|
{% if m.can_be_edited_by(user) %}
|
||||||
@@ -65,10 +65,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in profile.memberships.exclude(end_date=None).all() %}
|
{% for m in profile.memberships.ongoing().select_related("role") %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url('club:club_members', club_id=m.club.id) }}">{{ m.club }}</a></td>
|
<td><a href="{{ url('club:club_members', club_id=m.club.id) }}">{{ m.club }}</a></td>
|
||||||
<td>{{ settings.SITH_CLUB_ROLES[m.role] }}</td>
|
<td>{{ m.role.name }}</td>
|
||||||
<td>{{ m.description }}</td>
|
<td>{{ m.description }}</td>
|
||||||
<td>{{ m.start_date }}</td>
|
<td>{{ m.start_date }}</td>
|
||||||
<td>{{ m.end_date }}</td>
|
<td>{{ m.end_date }}</td>
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ class Counter(models.Model):
|
|||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
mem = self.club.get_membership_for(user)
|
mem = self.club.get_membership_for(user)
|
||||||
if mem and mem.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
if mem and mem.role.is_presidency:
|
||||||
return True
|
return True
|
||||||
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||||
|
|
||||||
|
|||||||
@@ -574,30 +574,6 @@ SITH_SUBSCRIPTIONS = {
|
|||||||
# To be completed....
|
# To be completed....
|
||||||
}
|
}
|
||||||
|
|
||||||
SITH_CLUB_ROLES_ID = {
|
|
||||||
"President": 10,
|
|
||||||
"Vice-President": 9,
|
|
||||||
"Treasurer": 7,
|
|
||||||
"Communication supervisor": 5,
|
|
||||||
"Secretary": 4,
|
|
||||||
"IT supervisor": 3,
|
|
||||||
"Board member": 2,
|
|
||||||
"Active member": 1,
|
|
||||||
"Curious": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
SITH_CLUB_ROLES = {
|
|
||||||
10: _("President"),
|
|
||||||
9: _("Vice-President"),
|
|
||||||
7: _("Treasurer"),
|
|
||||||
5: _("Communication supervisor"),
|
|
||||||
4: _("Secretary"),
|
|
||||||
3: _("IT supervisor"),
|
|
||||||
2: _("Board member"),
|
|
||||||
1: _("Active member"),
|
|
||||||
0: _("Curious"),
|
|
||||||
}
|
|
||||||
|
|
||||||
# This corresponds to the maximum role a user can freely subscribe to
|
# This corresponds to the maximum role a user can freely subscribe to
|
||||||
# In this case, SITH_MAXIMUM_FREE_ROLE=1 means that a user can
|
# In this case, SITH_MAXIMUM_FREE_ROLE=1 means that a user can
|
||||||
# set himself as "Membre actif" or "Curieux", but not higher
|
# set himself as "Membre actif" or "Curieux", but not higher
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -152,10 +151,12 @@ class TrombiUser(models.Model):
|
|||||||
|
|
||||||
def make_memberships(self):
|
def make_memberships(self):
|
||||||
self.memberships.all().delete()
|
self.memberships.all().delete()
|
||||||
for m in self.user.memberships.filter(
|
for m in (
|
||||||
role__gt=settings.SITH_MAXIMUM_FREE_ROLE
|
self.user.memberships.filter(role__is_board=True)
|
||||||
).order_by("end_date"):
|
.select_related("role")
|
||||||
role = str(settings.SITH_CLUB_ROLES[m.role])
|
.order_by("end_date")
|
||||||
|
):
|
||||||
|
role = m.role.name
|
||||||
if m.description:
|
if m.description:
|
||||||
role += " (%s)" % m.description
|
role += " (%s)" % m.description
|
||||||
end_date = get_semester_code(m.end_date) if m.end_date else ""
|
end_date = get_semester_code(m.end_date) if m.end_date else ""
|
||||||
|
|||||||
Reference in New Issue
Block a user