mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-22 03:25:05 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3700fbb214 | ||
|
|
b2b8d24003 | ||
|
|
d3c6402e28 | ||
|
|
74679c86d1 | ||
|
|
5275c30480 | ||
|
|
7e6d5fc435 | ||
|
|
ffa0b94408 | ||
|
|
22a1f4ba07 | ||
|
|
1c0b89bfc7 | ||
|
|
f1a60e589a |
@@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Club)
|
@admin.register(Club)
|
||||||
@@ -30,6 +30,20 @@ class ClubAdmin(admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ClubRole)
|
||||||
|
class ClubRoleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("name", "club", "is_board", "is_presidency")
|
||||||
|
search_fields = ("name",)
|
||||||
|
autocomplete_fields = ("club",)
|
||||||
|
list_select_related = ("club",)
|
||||||
|
list_filter = (
|
||||||
|
"is_board",
|
||||||
|
"is_presidency",
|
||||||
|
("club", admin.RelatedOnlyFieldListFilter),
|
||||||
|
)
|
||||||
|
show_facets = admin.ModelAdmin.show_facets.ALWAYS
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Membership)
|
@admin.register(Membership)
|
||||||
class MembershipAdmin(admin.ModelAdmin):
|
class MembershipAdmin(admin.ModelAdmin):
|
||||||
list_display = ("user", "club", "role", "start_date", "end_date")
|
list_display = ("user", "club", "role", "start_date", "end_date")
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ class ClubController(ControllerBase):
|
|||||||
)
|
)
|
||||||
def fetch_club(self, club_id: int):
|
def fetch_club(self, club_id: int):
|
||||||
prefetch = Prefetch(
|
prefetch = Prefetch(
|
||||||
"members", queryset=Membership.objects.ongoing().select_related("user")
|
"members",
|
||||||
|
queryset=Membership.objects.ongoing().select_related("user", "role"),
|
||||||
)
|
)
|
||||||
return self.get_object_or_exception(
|
return self.get_object_or_exception(
|
||||||
Club.objects.prefetch_related(prefetch), id=club_id
|
Club.objects.prefetch_related(prefetch), id=club_id
|
||||||
@@ -61,5 +62,5 @@ class UserClubController(ControllerBase):
|
|||||||
return (
|
return (
|
||||||
Membership.objects.ongoing()
|
Membership.objects.ongoing()
|
||||||
.filter(user=user)
|
.filter(user=user)
|
||||||
.select_related("club", "user")
|
.select_related("club", "user", "role")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.db.models.functions.datetime
|
import django.db.models.functions.datetime
|
||||||
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 Q
|
from django.db.models import Q
|
||||||
from django.utils.timezone import localdate
|
from django.utils.timezone import localdate
|
||||||
|
|
||||||
|
# Before the club role rework, the maximum free role
|
||||||
|
# was the hardcoded highest non-board role
|
||||||
|
MAXIMUM_FREE_ROLE = 1
|
||||||
|
|
||||||
|
|
||||||
def migrate_meta_groups(apps: StateApps, schema_editor):
|
def migrate_meta_groups(apps: StateApps, schema_editor):
|
||||||
"""Attach the existing meta groups to the clubs.
|
"""Attach the existing meta groups to the clubs.
|
||||||
@@ -46,10 +49,7 @@ def migrate_meta_groups(apps: StateApps, schema_editor):
|
|||||||
).select_related("user")
|
).select_related("user")
|
||||||
club.members_group.users.set([m.user for m in memberships])
|
club.members_group.users.set([m.user for m in memberships])
|
||||||
club.board_group.users.set(
|
club.board_group.users.set(
|
||||||
[
|
[m.user for m in memberships.filter(role__gt=MAXIMUM_FREE_ROLE)]
|
||||||
m.user
|
|
||||||
for m in memberships.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
138
club/migrations/0015_clubrole_alter_membership_role.py
Normal file
138
club/migrations/0015_clubrole_alter_membership_role.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# Generated by Django 5.2.3 on 2025-06-21 21:59
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
from django.db.models import Case, When
|
||||||
|
|
||||||
|
PRESIDENT_ROLE = 10
|
||||||
|
MAXIMUM_FREE_ROLE = 1
|
||||||
|
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):
|
||||||
|
ClubRole = apps.get_model("club", "ClubRole")
|
||||||
|
Membership = apps.get_model("club", "Membership")
|
||||||
|
|
||||||
|
updates = []
|
||||||
|
for club_id, role in Membership.objects.values_list("club", "role").distinct():
|
||||||
|
new_role = ClubRole.objects.create(
|
||||||
|
name=SITH_CLUB_ROLES[role],
|
||||||
|
is_board=role > MAXIMUM_FREE_ROLE,
|
||||||
|
is_presidency=role == PRESIDENT_ROLE,
|
||||||
|
club_id=club_id,
|
||||||
|
order=PRESIDENT_ROLE - role,
|
||||||
|
)
|
||||||
|
updates.append(When(role=role, then=new_role.id))
|
||||||
|
# all updates must happen at the same time
|
||||||
|
# otherwise, the 10 first created ClubRole would be
|
||||||
|
# re-modified after their initial creation, and it would
|
||||||
|
# result in an incoherent state.
|
||||||
|
# To avoid that, all updates are wrapped in a single giant Case(When) statement
|
||||||
|
# cf. https://docs.djangoproject.com/fr/stable/ref/models/conditional-expressions/#conditional-update
|
||||||
|
Membership.objects.update(role=Case(*updates))
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("club", "0014_alter_club_options_rename_unix_name_club_slug_name_and_more"),
|
||||||
|
("core", "0047_alter_notification_date_alter_notification_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="club",
|
||||||
|
name="page",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="club",
|
||||||
|
to="core.page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ClubRole",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"order",
|
||||||
|
models.PositiveIntegerField(
|
||||||
|
db_index=True, editable=False, verbose_name="order"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"club",
|
||||||
|
models.ForeignKey(
|
||||||
|
help_text="The club in which this role exists",
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="roles",
|
||||||
|
to="club.club",
|
||||||
|
verbose_name="club",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=50, verbose_name="name")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(
|
||||||
|
default="", blank=True, verbose_name="description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_board",
|
||||||
|
models.BooleanField(default=False, verbose_name="Board role"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_presidency",
|
||||||
|
models.BooleanField(default=False, verbose_name="Presidency role"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_active",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text=(
|
||||||
|
"If the role is inactive, people joining the club "
|
||||||
|
"won't be able to get it."
|
||||||
|
),
|
||||||
|
verbose_name="is active",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"ordering": ("order",),
|
||||||
|
"verbose_name": "club role",
|
||||||
|
"verbose_name_plural": "club roles",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="clubrole",
|
||||||
|
constraint=models.CheckConstraint(
|
||||||
|
condition=models.Q(
|
||||||
|
("is_presidency", False), ("is_board", True), _connector="OR"
|
||||||
|
),
|
||||||
|
name="clubrole_presidency_implies_board",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_roles, migrations.RunPython.noop),
|
||||||
|
# because Postgres migrations run in a single transaction,
|
||||||
|
# we cannot change the actual values of Membership.role
|
||||||
|
# and apply the FOREIGN KEY constraint in the same migration.
|
||||||
|
# The constraint is created in the next migration
|
||||||
|
]
|
||||||
25
club/migrations/0016_clubrole_alter_membership_role.py
Normal file
25
club/migrations/0016_clubrole_alter_membership_role.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.3 on 2025-09-27 09:57
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [("club", "0015_clubrole_alter_membership_role")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
# because Postgres migrations run in a single transaction,
|
||||||
|
# we cannot change the actual values of Membership.role
|
||||||
|
# and apply the FOREIGN KEY constraint in the same migration.
|
||||||
|
# The data migration was made in the previous migration.
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="membership",
|
||||||
|
name="role",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="members",
|
||||||
|
to="club.clubrole",
|
||||||
|
verbose_name="role",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
124
club/models.py
124
club/models.py
@@ -29,14 +29,14 @@ 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 models, transaction
|
||||||
from django.db.models import Exists, F, OuterRef, Q, Value
|
from django.db.models import Exists, F, OuterRef, Q
|
||||||
from django.db.models.functions import Greatest
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.timezone import localdate
|
from django.utils.timezone import localdate
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from ordered_model.models import OrderedModel
|
||||||
|
|
||||||
from core.fields import ResizedImageField
|
from core.fields import ResizedImageField
|
||||||
from core.models import Group, Notification, Page, SithFile, User
|
from core.models import Group, Notification, Page, SithFile, User
|
||||||
@@ -89,7 +89,7 @@ class Club(models.Model):
|
|||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
page = models.OneToOneField(
|
page = models.OneToOneField(
|
||||||
Page, related_name="club", blank=True, on_delete=models.CASCADE
|
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
|
||||||
@@ -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."""
|
||||||
@@ -220,6 +220,77 @@ class Club(models.Model):
|
|||||||
return user.is_in_group(pk=self.board_group_id)
|
return user.is_in_group(pk=self.board_group_id)
|
||||||
|
|
||||||
|
|
||||||
|
class ClubRole(OrderedModel):
|
||||||
|
club = models.ForeignKey(
|
||||||
|
Club,
|
||||||
|
verbose_name=_("club"),
|
||||||
|
help_text=_("The club in which this role exists"),
|
||||||
|
related_name="roles",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
name = models.CharField(_("name"), max_length=50)
|
||||||
|
description = models.TextField(_("description"), blank=True, default="")
|
||||||
|
is_board = models.BooleanField(_("Board role"), default=False)
|
||||||
|
is_presidency = models.BooleanField(_("Presidency role"), default=False)
|
||||||
|
is_active = models.BooleanField(
|
||||||
|
_("is active"),
|
||||||
|
default=True,
|
||||||
|
help_text=_(
|
||||||
|
"If the role is inactive, people joining the club won't be able to get it."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
order_with_respect_to = "club"
|
||||||
|
|
||||||
|
class Meta(OrderedModel.Meta):
|
||||||
|
verbose_name = _("club role")
|
||||||
|
verbose_name_plural = _("club roles")
|
||||||
|
abstract = False
|
||||||
|
constraints = [
|
||||||
|
# presidency IMPLIES board <=> NOT presidency OR board
|
||||||
|
# cf. MT1 :)
|
||||||
|
models.CheckConstraint(
|
||||||
|
condition=Q(is_presidency=False) | Q(is_board=True),
|
||||||
|
name="clubrole_presidency_implies_board",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_display_name(self):
|
||||||
|
return f"{self.name} - {self.club.name}"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("club:club_roles", kwargs={"club_id": self.club_id})
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
errors = []
|
||||||
|
if self.is_presidency and not self.is_board:
|
||||||
|
errors.append(
|
||||||
|
ValidationError(
|
||||||
|
_(
|
||||||
|
"Role %(name)s was declared as a presidency role "
|
||||||
|
"without being a board role"
|
||||||
|
)
|
||||||
|
% {"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()
|
||||||
|
|
||||||
|
|
||||||
class MembershipQuerySet(models.QuerySet):
|
class MembershipQuerySet(models.QuerySet):
|
||||||
def ongoing(self) -> Self:
|
def ongoing(self) -> Self:
|
||||||
"""Filter all memberships which are not finished yet."""
|
"""Filter all memberships which are not finished yet."""
|
||||||
@@ -232,9 +303,10 @@ class MembershipQuerySet(models.QuerySet):
|
|||||||
are included, even if there are no more members.
|
are included, even if there are no more members.
|
||||||
|
|
||||||
If you want to get the users who are currently in the board,
|
If you want to get the users who are currently in the board,
|
||||||
mind combining this with the `ongoing` queryset method
|
mind combining this with the [MembershipQuerySet.ongoing][]
|
||||||
|
queryset method
|
||||||
"""
|
"""
|
||||||
return self.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE)
|
return self.filter(role__is_board=True)
|
||||||
|
|
||||||
def editable_by(self, user: User) -> Self:
|
def editable_by(self, user: User) -> Self:
|
||||||
"""Filter Memberships that this user can edit.
|
"""Filter Memberships that this user can edit.
|
||||||
@@ -257,21 +329,16 @@ class MembershipQuerySet(models.QuerySet):
|
|||||||
"""
|
"""
|
||||||
if user.has_perm("club.change_membership"):
|
if user.has_perm("club.change_membership"):
|
||||||
return self.all()
|
return self.all()
|
||||||
return self.filter(
|
return self.ongoing().filter(
|
||||||
Q(user=user)
|
Q(user=user)
|
||||||
| Exists(
|
| Exists(
|
||||||
Membership.objects.filter(
|
Membership.objects.ongoing().filter(
|
||||||
Q(
|
|
||||||
role__gt=Greatest(
|
|
||||||
OuterRef("role"), Value(settings.SITH_MAXIMUM_FREE_ROLE)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
user=user,
|
user=user,
|
||||||
end_date=None,
|
|
||||||
club=OuterRef("club"),
|
club=OuterRef("club"),
|
||||||
|
role__is_board=True,
|
||||||
|
role__order__lt=OuterRef("role__order"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
|
||||||
end_date=None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(self, **kwargs) -> int:
|
def update(self, **kwargs) -> int:
|
||||||
@@ -341,10 +408,11 @@ class Membership(models.Model):
|
|||||||
)
|
)
|
||||||
start_date = models.DateField(_("start date"), default=timezone.now)
|
start_date = models.DateField(_("start date"), default=timezone.now)
|
||||||
end_date = models.DateField(_("end date"), null=True, blank=True)
|
end_date = models.DateField(_("end date"), null=True, blank=True)
|
||||||
role = models.IntegerField(
|
role = models.ForeignKey(
|
||||||
_("role"),
|
ClubRole,
|
||||||
choices=sorted(settings.SITH_CLUB_ROLES.items()),
|
verbose_name=_("role"),
|
||||||
default=sorted(settings.SITH_CLUB_ROLES.items())[0][0],
|
related_name="members",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
_("description"), max_length=128, null=False, blank=True
|
_("description"), max_length=128, null=False, blank=True
|
||||||
@@ -362,7 +430,7 @@ class Membership(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return (
|
||||||
f"{self.club.name} - {self.user.username} "
|
f"{self.club.name} - {self.user.username} "
|
||||||
f"- {settings.SITH_CLUB_ROLES[self.role]} "
|
f"- {self.role.name} "
|
||||||
f"- {str(_('past member')) if self.end_date is not None else ''}"
|
f"- {str(_('past member')) if self.end_date is not None else ''}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -391,7 +459,11 @@ class Membership(models.Model):
|
|||||||
if user.is_root or user.is_board_member:
|
if user.is_root or user.is_board_member:
|
||||||
return True
|
return True
|
||||||
membership = self.club.get_membership_for(user)
|
membership = self.club.get_membership_for(user)
|
||||||
return membership is not None and membership.role >= self.role
|
if not membership:
|
||||||
|
return False
|
||||||
|
return membership.user_id == user.id or (
|
||||||
|
membership.is_board and membership.role.order < self.role.order
|
||||||
|
)
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
self._remove_club_groups([self])
|
self._remove_club_groups([self])
|
||||||
@@ -467,7 +539,7 @@ class Membership(models.Model):
|
|||||||
group_id=membership.club.members_group_id,
|
group_id=membership.club.members_group_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if membership.role > settings.SITH_MAXIMUM_FREE_ROLE:
|
if membership.role.is_board:
|
||||||
club_groups.append(
|
club_groups.append(
|
||||||
User.groups.through(
|
User.groups.through(
|
||||||
user_id=membership.user_id,
|
user_id=membership.user_id,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Annotated
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from ninja import FilterLookup, FilterSchema, ModelSchema
|
from ninja import FilterLookup, FilterSchema, ModelSchema
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.schemas import NonEmptyStr, SimpleUserSchema
|
from core.schemas import NonEmptyStr, SimpleUserSchema
|
||||||
|
|
||||||
|
|
||||||
@@ -39,14 +39,21 @@ class ClubProfileSchema(ModelSchema):
|
|||||||
return obj.get_absolute_url()
|
return obj.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
class ClubRoleSchema(ModelSchema):
|
||||||
|
class Meta:
|
||||||
|
model = ClubRole
|
||||||
|
fields = ["id", "name", "is_presidency", "is_board"]
|
||||||
|
|
||||||
|
|
||||||
class ClubMemberSchema(ModelSchema):
|
class ClubMemberSchema(ModelSchema):
|
||||||
"""A schema to represent all memberships in a club."""
|
"""A schema to represent all memberships in a club."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Membership
|
model = Membership
|
||||||
fields = ["start_date", "end_date", "role", "description"]
|
fields = ["start_date", "end_date", "description"]
|
||||||
|
|
||||||
user: SimpleUserSchema
|
user: SimpleUserSchema
|
||||||
|
role: ClubRoleSchema
|
||||||
|
|
||||||
|
|
||||||
class ClubSchema(ModelSchema):
|
class ClubSchema(ModelSchema):
|
||||||
@@ -62,6 +69,7 @@ class UserMembershipSchema(ModelSchema):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Membership
|
model = Membership
|
||||||
fields = ["id", "start_date", "role", "description"]
|
fields = ["id", "start_date", "description"]
|
||||||
|
|
||||||
club: SimpleClubSchema
|
club: SimpleClubSchema
|
||||||
|
role: ClubRoleSchema
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.utils.timezone import now
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from model_bakery.recipe import Recipe
|
from model_bakery.recipe import Recipe
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.baker_recipes import old_subscriber_user, subscriber_user
|
from core.baker_recipes import old_subscriber_user, subscriber_user
|
||||||
from core.models import User
|
from core.models import User
|
||||||
|
|
||||||
@@ -43,6 +43,11 @@ class TestClub(TestCase):
|
|||||||
|
|
||||||
cls.ae = Club.objects.get(pk=settings.SITH_MAIN_CLUB_ID)
|
cls.ae = Club.objects.get(pk=settings.SITH_MAIN_CLUB_ID)
|
||||||
cls.club = baker.make(Club)
|
cls.club = baker.make(Club)
|
||||||
|
cls.president_role = baker.make(
|
||||||
|
ClubRole, club=cls.club, is_board=True, is_presidency=True, order=0
|
||||||
|
)
|
||||||
|
cls.board_role = baker.make(ClubRole, club=cls.club, is_board=True, order=1)
|
||||||
|
cls.member_role = baker.make(ClubRole, club=cls.club, order=2)
|
||||||
cls.new_members_url = reverse(
|
cls.new_members_url = reverse(
|
||||||
"club:club_new_members", kwargs={"club_id": cls.club.id}
|
"club:club_new_members", kwargs={"club_id": cls.club.id}
|
||||||
)
|
)
|
||||||
@@ -51,12 +56,17 @@ class TestClub(TestCase):
|
|||||||
yesterday = now() - timedelta(days=1)
|
yesterday = now() - timedelta(days=1)
|
||||||
membership_recipe = Recipe(Membership, club=cls.club)
|
membership_recipe = Recipe(Membership, club=cls.club)
|
||||||
membership_recipe.make(
|
membership_recipe.make(
|
||||||
user=cls.simple_board_member, start_date=a_month_ago, role=3
|
user=cls.simple_board_member, start_date=a_month_ago, role=cls.board_role
|
||||||
|
)
|
||||||
|
membership_recipe.make(user=cls.richard, role=cls.member_role)
|
||||||
|
membership_recipe.make(
|
||||||
|
user=cls.president, start_date=a_month_ago, role=cls.president_role
|
||||||
)
|
)
|
||||||
membership_recipe.make(user=cls.richard, role=1)
|
|
||||||
membership_recipe.make(user=cls.president, start_date=a_month_ago, role=10)
|
|
||||||
membership_recipe.make( # sli was a member but isn't anymore
|
membership_recipe.make( # sli was a member but isn't anymore
|
||||||
user=cls.sli, start_date=a_month_ago, end_date=yesterday, role=2
|
user=cls.sli,
|
||||||
|
start_date=a_month_ago,
|
||||||
|
end_date=yesterday,
|
||||||
|
role=cls.board_role,
|
||||||
)
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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 club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
|
|
||||||
|
|
||||||
@@ -16,11 +16,19 @@ def test_club_queryset_having_board_member():
|
|||||||
membership_recipe = Recipe(
|
membership_recipe = Recipe(
|
||||||
Membership, user=user, start_date=localdate() - timedelta(days=3)
|
Membership, user=user, start_date=localdate() - timedelta(days=3)
|
||||||
)
|
)
|
||||||
membership_recipe.make(club=clubs[0], role=1)
|
|
||||||
membership_recipe.make(club=clubs[1], role=3)
|
|
||||||
membership_recipe.make(club=clubs[2], role=7)
|
|
||||||
membership_recipe.make(
|
membership_recipe.make(
|
||||||
club=clubs[3], role=3, end_date=localdate() - timedelta(days=1)
|
club=clubs[0], role=baker.make(ClubRole, club=clubs[0], is_board=False)
|
||||||
|
)
|
||||||
|
membership_recipe.make(
|
||||||
|
club=clubs[1], role=baker.make(ClubRole, club=clubs[1], is_board=True)
|
||||||
|
)
|
||||||
|
membership_recipe.make(
|
||||||
|
club=clubs[2], role=baker.make(ClubRole, club=clubs[2], is_board=True)
|
||||||
|
)
|
||||||
|
membership_recipe.make(
|
||||||
|
club=clubs[3],
|
||||||
|
role=baker.make(ClubRole, club=clubs[3], is_board=True),
|
||||||
|
end_date=localdate() - timedelta(days=1),
|
||||||
)
|
)
|
||||||
|
|
||||||
club_ids = Club.objects.having_board_member(user).values_list("id", flat=True)
|
club_ids = Club.objects.having_board_member(user).values_list("id", flat=True)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -8,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
|
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
|
||||||
|
|
||||||
@@ -26,8 +27,10 @@ class TestClubSearch(TestCase):
|
|||||||
"id", flat=True
|
"id", flat=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Page.objects.exclude(club=None).delete()
|
Membership.objects.all().delete()
|
||||||
|
ClubRole.objects.all().delete()
|
||||||
Club.objects.all().delete()
|
Club.objects.all().delete()
|
||||||
|
Page.objects.exclude(name=settings.SITH_CLUB_ROOT_PAGE).delete()
|
||||||
Group.objects.filter(id__in=groups).delete()
|
Group.objects.filter(id__in=groups).delete()
|
||||||
|
|
||||||
cls.clubs = baker.make(
|
cls.clubs = baker.make(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.urls import reverse
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from pytest_django.asserts import assertRedirects
|
from pytest_django.asserts import assertRedirects
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +12,12 @@ from core.baker_recipes import subscriber_user
|
|||||||
def test_club_board_member_cannot_edit_club_properties(client: Client):
|
def test_club_board_member_cannot_edit_club_properties(client: Client):
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
club = baker.make(Club, name="old name", is_active=True, address="old address")
|
club = baker.make(Club, name="old name", is_active=True, address="old address")
|
||||||
baker.make(Membership, club=club, user=user, role=7)
|
baker.make(
|
||||||
|
Membership,
|
||||||
|
club=club,
|
||||||
|
user=user,
|
||||||
|
role=baker.make(ClubRole, club=club, is_board=True),
|
||||||
|
)
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
res = client.post(
|
res = client.post(
|
||||||
reverse("club:club_edit", kwargs={"club_id": club.id}),
|
reverse("club:club_edit", kwargs={"club_id": club.id}),
|
||||||
@@ -32,7 +37,12 @@ def test_edit_club_page_doesnt_crash(client: Client):
|
|||||||
"""crash test for club:club_edit"""
|
"""crash test for club:club_edit"""
|
||||||
club = baker.make(Club)
|
club = baker.make(Club)
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
baker.make(Membership, club=club, user=user, role=3)
|
baker.make(
|
||||||
|
Membership,
|
||||||
|
club=club,
|
||||||
|
user=user,
|
||||||
|
role=baker.make(ClubRole, club=club, is_board=True),
|
||||||
|
)
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
res = client.get(reverse("club:club_edit", kwargs={"club_id": club.id}))
|
res = client.get(reverse("club:club_edit", kwargs={"club_id": club.id}))
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ from django.test import TestCase
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
from club.forms import MailingForm
|
from club.forms import MailingForm
|
||||||
from club.models import Club, Mailing, Membership
|
from club.models import Club, ClubRole, Mailing, Membership
|
||||||
from core.models import User
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ class TestMailingForm(TestCase):
|
|||||||
user=cls.rbatsbak,
|
user=cls.rbatsbak,
|
||||||
club=cls.club,
|
club=cls.club,
|
||||||
start_date=timezone.now(),
|
start_date=timezone.now(),
|
||||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
role=baker.make(ClubRole, club=cls.club, is_board=True),
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
def test_mailing_list_add_no_moderation(self):
|
def test_mailing_list_add_no_moderation(self):
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import itertools
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Max
|
from django.db.models import Max
|
||||||
@@ -14,7 +14,7 @@ from model_bakery import baker
|
|||||||
from pytest_django.asserts import assertRedirects
|
from pytest_django.asserts import assertRedirects
|
||||||
|
|
||||||
from club.forms import ClubAddMemberForm, JoinClubForm
|
from club.forms import ClubAddMemberForm, JoinClubForm
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from club.tests.base import TestClub
|
from club.tests.base import TestClub
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
from core.models import AnonymousUser, User
|
from core.models import AnonymousUser, User
|
||||||
@@ -75,17 +75,22 @@ class TestMembershipQuerySet(TestClub):
|
|||||||
def test_update_change_club_groups(self):
|
def test_update_change_club_groups(self):
|
||||||
"""Test that `update` set the user groups accordingly."""
|
"""Test that `update` set the user groups accordingly."""
|
||||||
user = baker.make(User)
|
user = baker.make(User)
|
||||||
membership = baker.make(Membership, end_date=None, user=user, role=5)
|
board_role, member_role = baker.make(
|
||||||
|
ClubRole, is_board=iter([True, False]), _quantity=2, _bulk_create=True
|
||||||
|
)
|
||||||
|
membership = baker.make(
|
||||||
|
Membership, end_date=None, user=user, role=board_role, club=board_role.club
|
||||||
|
)
|
||||||
members_group = membership.club.members_group
|
members_group = membership.club.members_group
|
||||||
board_group = membership.club.board_group
|
board_group = membership.club.board_group
|
||||||
assert user.groups.contains(members_group)
|
assert user.groups.contains(members_group)
|
||||||
assert user.groups.contains(board_group)
|
assert user.groups.contains(board_group)
|
||||||
|
|
||||||
user.memberships.update(role=1) # from board to simple member
|
user.memberships.update(role=member_role) # from board to simple member
|
||||||
assert user.groups.contains(members_group)
|
assert user.groups.contains(members_group)
|
||||||
assert not user.groups.contains(board_group)
|
assert not user.groups.contains(board_group)
|
||||||
|
|
||||||
user.memberships.update(role=5) # from member to board
|
user.memberships.update(role=board_role) # from member to board
|
||||||
assert user.groups.contains(members_group)
|
assert user.groups.contains(members_group)
|
||||||
assert user.groups.contains(board_group)
|
assert user.groups.contains(board_group)
|
||||||
|
|
||||||
@@ -96,7 +101,17 @@ class TestMembershipQuerySet(TestClub):
|
|||||||
def test_delete_remove_from_groups(self):
|
def test_delete_remove_from_groups(self):
|
||||||
"""Test that `delete` removes from club groups"""
|
"""Test that `delete` removes from club groups"""
|
||||||
user = baker.make(User)
|
user = baker.make(User)
|
||||||
memberships = baker.make(Membership, role=iter([1, 5]), user=user, _quantity=2)
|
club = baker.make(Club)
|
||||||
|
roles = baker.make(
|
||||||
|
ClubRole,
|
||||||
|
is_board=iter([False, True]),
|
||||||
|
club=club,
|
||||||
|
_quantity=2,
|
||||||
|
_bulk_create=True,
|
||||||
|
)
|
||||||
|
memberships = baker.make(
|
||||||
|
Membership, club=club, role=iter(roles), user=user, _quantity=2
|
||||||
|
)
|
||||||
club_groups = {
|
club_groups = {
|
||||||
memberships[0].club.members_group,
|
memberships[0].club.members_group,
|
||||||
memberships[1].club.members_group,
|
memberships[1].club.members_group,
|
||||||
@@ -112,13 +127,20 @@ class TestMembershipEditableBy(TestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
Membership.objects.all().delete()
|
Membership.objects.all().delete()
|
||||||
cls.club_a, cls.club_b = baker.make(Club, _quantity=2)
|
cls.club_a, cls.club_b = baker.make(Club, _quantity=2)
|
||||||
|
roles = baker.make(
|
||||||
|
ClubRole,
|
||||||
|
is_presidency=itertools.cycle([True, False, False, False]),
|
||||||
|
is_board=itertools.cycle([True, True, True, False]),
|
||||||
|
order=itertools.cycle(range(4)),
|
||||||
|
club=iter(
|
||||||
|
[*itertools.repeat(cls.club_a, 4), *itertools.repeat(cls.club_b, 4)]
|
||||||
|
),
|
||||||
|
_quantity=8,
|
||||||
|
_bulk_create=True,
|
||||||
|
)
|
||||||
cls.memberships = [
|
cls.memberships = [
|
||||||
*baker.make(
|
*baker.make(Membership, role=iter(roles[:4]), club=cls.club_a, _quantity=4),
|
||||||
Membership, role=iter([7, 3, 3, 1]), club=cls.club_a, _quantity=4
|
*baker.make(Membership, role=iter(roles[4:]), club=cls.club_b, _quantity=4),
|
||||||
),
|
|
||||||
*baker.make(
|
|
||||||
Membership, role=iter([7, 3, 3, 1]), club=cls.club_b, _quantity=4
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_admin_user(self):
|
def test_admin_user(self):
|
||||||
@@ -140,7 +162,7 @@ class TestMembershipEditableBy(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestMembership(TestClub):
|
class TestMembership(TestClub):
|
||||||
def assert_membership_started_today(self, user: User, role: int):
|
def assert_membership_started_today(self, user: User, role: ClubRole):
|
||||||
"""Assert that the given membership is active and started today."""
|
"""Assert that the given membership is active and started today."""
|
||||||
membership = user.memberships.ongoing().filter(club=self.club).first()
|
membership = user.memberships.ongoing().filter(club=self.club).first()
|
||||||
assert membership is not None
|
assert membership is not None
|
||||||
@@ -189,21 +211,27 @@ class TestMembership(TestClub):
|
|||||||
"Marquer comme ancien",
|
"Marquer comme ancien",
|
||||||
]
|
]
|
||||||
rows = table.find("tbody").find_all("tr")
|
rows = table.find("tbody").find_all("tr")
|
||||||
memberships = self.club.members.ongoing().order_by("-role")
|
memberships = (
|
||||||
for row, membership in zip(
|
self.club.members.ongoing()
|
||||||
rows, memberships.select_related("user"), strict=False
|
.order_by("role__order")
|
||||||
):
|
.select_related("user", "role")
|
||||||
|
)
|
||||||
|
user_role = ClubRole.objects.get(members__user=self.simple_board_member)
|
||||||
|
for row, membership in zip(rows, memberships, strict=False):
|
||||||
user = membership.user
|
user = membership.user
|
||||||
user_url = reverse("core:user_profile", args=[user.id])
|
user_url = reverse("core:user_profile", args=[user.id])
|
||||||
cols = row.find_all("td")
|
cols = row.find_all("td")
|
||||||
user_link = cols[0].find("a")
|
user_link = cols[0].find("a")
|
||||||
assert user_link.attrs["href"] == user_url
|
assert user_link.attrs["href"] == user_url
|
||||||
assert user_link.text == user.get_display_name()
|
assert user_link.text == user.get_display_name()
|
||||||
assert cols[1].text == settings.SITH_CLUB_ROLES[membership.role]
|
assert cols[1].text == membership.role.name
|
||||||
assert cols[2].text == membership.description
|
assert cols[2].text == membership.description
|
||||||
assert cols[3].text == str(membership.start_date)
|
assert cols[3].text == str(membership.start_date)
|
||||||
|
|
||||||
if membership.role < 3 or membership.user_id == self.simple_board_member.id:
|
if (
|
||||||
|
membership.role.order > user_role.order
|
||||||
|
or membership.user_id == self.simple_board_member.id
|
||||||
|
):
|
||||||
# 3 is the role of simple_board_member
|
# 3 is the role of simple_board_member
|
||||||
form_input = cols[4].find("input")
|
form_input = cols[4].find("input")
|
||||||
expected_attrs = {
|
expected_attrs = {
|
||||||
@@ -219,14 +247,15 @@ class TestMembership(TestClub):
|
|||||||
"""Test that root users can add members to clubs"""
|
"""Test that root users can add members to clubs"""
|
||||||
self.client.force_login(self.root)
|
self.client.force_login(self.root)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.new_members_url, {"user": self.subscriber.id, "role": 3}
|
self.new_members_url,
|
||||||
|
{"user": self.subscriber.id, "role": self.board_role.id},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.headers.get("HX-Redirect", "") == reverse(
|
assert response.headers.get("HX-Redirect", "") == reverse(
|
||||||
"club:club_members", kwargs={"club_id": self.club.id}
|
"club:club_members", kwargs={"club_id": self.club.id}
|
||||||
)
|
)
|
||||||
self.subscriber.refresh_from_db()
|
self.subscriber.refresh_from_db()
|
||||||
self.assert_membership_started_today(self.subscriber, role=3)
|
self.assert_membership_started_today(self.subscriber, role=self.board_role)
|
||||||
|
|
||||||
def test_add_unauthorized_members(self):
|
def test_add_unauthorized_members(self):
|
||||||
"""Test that users who are not currently subscribed
|
"""Test that users who are not currently subscribed
|
||||||
@@ -234,7 +263,7 @@ class TestMembership(TestClub):
|
|||||||
"""
|
"""
|
||||||
for user in self.public, self.old_subscriber:
|
for user in self.public, self.old_subscriber:
|
||||||
form = ClubAddMemberForm(
|
form = ClubAddMemberForm(
|
||||||
data={"user": user.id, "role": 1},
|
data={"user": user.id, "role": self.member_role},
|
||||||
request_user=self.root,
|
request_user=self.root,
|
||||||
club=self.club,
|
club=self.club,
|
||||||
)
|
)
|
||||||
@@ -255,7 +284,7 @@ class TestMembership(TestClub):
|
|||||||
nb_memberships = self.simple_board_member.memberships.count()
|
nb_memberships = self.simple_board_member.memberships.count()
|
||||||
self.client.post(
|
self.client.post(
|
||||||
self.members_url,
|
self.members_url,
|
||||||
{"users": self.simple_board_member.id, "role": current_membership.role + 1},
|
{"users": self.simple_board_member.id, "role": self.member_role},
|
||||||
)
|
)
|
||||||
self.simple_board_member.refresh_from_db()
|
self.simple_board_member.refresh_from_db()
|
||||||
assert nb_memberships == self.simple_board_member.memberships.count()
|
assert nb_memberships == self.simple_board_member.memberships.count()
|
||||||
@@ -274,7 +303,7 @@ class TestMembership(TestClub):
|
|||||||
max_id = User.objects.aggregate(id=Max("id"))["id"]
|
max_id = User.objects.aggregate(id=Max("id"))["id"]
|
||||||
for members in [max_id + 1], [max_id + 1, self.subscriber.id]:
|
for members in [max_id + 1], [max_id + 1, self.subscriber.id]:
|
||||||
form = ClubAddMemberForm(
|
form = ClubAddMemberForm(
|
||||||
data={"user": members, "role": 1},
|
data={"user": members, "role": self.member_role},
|
||||||
request_user=self.root,
|
request_user=self.root,
|
||||||
club=self.club,
|
club=self.club,
|
||||||
)
|
)
|
||||||
@@ -290,12 +319,13 @@ class TestMembership(TestClub):
|
|||||||
|
|
||||||
def test_president_add_members(self):
|
def test_president_add_members(self):
|
||||||
"""Test that the president of the club can add members."""
|
"""Test that the president of the club can add members."""
|
||||||
president = self.club.members.get(role=10).user
|
president = self.club.members.get(role=self.president_role).user
|
||||||
nb_club_membership = self.club.members.count()
|
nb_club_membership = self.club.members.count()
|
||||||
nb_subscriber_memberships = self.subscriber.memberships.count()
|
nb_subscriber_memberships = self.subscriber.memberships.count()
|
||||||
self.client.force_login(president)
|
self.client.force_login(president)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.new_members_url, {"user": self.subscriber.id, "role": 9}
|
self.new_members_url,
|
||||||
|
{"user": self.subscriber.id, "role": self.president_role.id},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.headers.get("HX-Redirect", "") == reverse(
|
assert response.headers.get("HX-Redirect", "") == reverse(
|
||||||
@@ -305,14 +335,17 @@ class TestMembership(TestClub):
|
|||||||
self.subscriber.refresh_from_db()
|
self.subscriber.refresh_from_db()
|
||||||
assert self.club.members.count() == nb_club_membership + 1
|
assert self.club.members.count() == nb_club_membership + 1
|
||||||
assert self.subscriber.memberships.count() == nb_subscriber_memberships + 1
|
assert self.subscriber.memberships.count() == nb_subscriber_memberships + 1
|
||||||
self.assert_membership_started_today(self.subscriber, role=9)
|
self.assert_membership_started_today(self.subscriber, role=self.president_role)
|
||||||
|
|
||||||
def test_add_member_greater_role(self):
|
def test_add_member_greater_role(self):
|
||||||
"""Test that a member of the club member cannot create
|
"""Test that a member of the club member cannot create
|
||||||
a membership with a greater role than its own.
|
a membership with a greater role than its own.
|
||||||
"""
|
"""
|
||||||
|
user_role = self.simple_board_member.memberships.first().role
|
||||||
|
other_role = baker.make(ClubRole, club=user_role.club, is_board=True)
|
||||||
|
other_role.above(user_role)
|
||||||
form = ClubAddMemberForm(
|
form = ClubAddMemberForm(
|
||||||
data={"user": self.subscriber.id, "role": 10},
|
data={"user": self.subscriber.id, "role": other_role.id},
|
||||||
request_user=self.simple_board_member,
|
request_user=self.simple_board_member,
|
||||||
club=self.club,
|
club=self.club,
|
||||||
)
|
)
|
||||||
@@ -320,7 +353,10 @@ class TestMembership(TestClub):
|
|||||||
|
|
||||||
assert not form.is_valid()
|
assert not form.is_valid()
|
||||||
assert form.errors == {
|
assert form.errors == {
|
||||||
"role": ["Sélectionnez un choix valide. 10 n\u2019en fait pas partie."]
|
"role": [
|
||||||
|
"Sélectionnez un choix valide. "
|
||||||
|
"Ce choix ne fait pas partie de ceux disponibles."
|
||||||
|
]
|
||||||
}
|
}
|
||||||
self.club.refresh_from_db()
|
self.club.refresh_from_db()
|
||||||
assert nb_memberships == self.club.members.count()
|
assert nb_memberships == self.club.members.count()
|
||||||
@@ -336,8 +372,9 @@ class TestMembership(TestClub):
|
|||||||
assert form.errors == {"role": ["Ce champ est obligatoire."]}
|
assert form.errors == {"role": ["Ce champ est obligatoire."]}
|
||||||
|
|
||||||
def test_add_member_already_there(self):
|
def test_add_member_already_there(self):
|
||||||
|
role = ClubRole.objects.get(members__user=self.simple_board_member)
|
||||||
form = ClubAddMemberForm(
|
form = ClubAddMemberForm(
|
||||||
data={"user": self.simple_board_member, "role": 3},
|
data={"user": self.simple_board_member, "role": role.id},
|
||||||
request_user=self.root,
|
request_user=self.root,
|
||||||
club=self.club,
|
club=self.club,
|
||||||
)
|
)
|
||||||
@@ -348,22 +385,27 @@ class TestMembership(TestClub):
|
|||||||
|
|
||||||
def test_add_other_member_forbidden(self):
|
def test_add_other_member_forbidden(self):
|
||||||
non_member = subscriber_user.make()
|
non_member = subscriber_user.make()
|
||||||
simple_member = baker.make(Membership, club=self.club, role=1).user
|
simple_member = baker.make(
|
||||||
|
Membership, club=self.club, role=self.member_role
|
||||||
|
).user
|
||||||
for user in non_member, simple_member:
|
for user in non_member, simple_member:
|
||||||
form = ClubAddMemberForm(
|
form = ClubAddMemberForm(
|
||||||
data={"user": subscriber_user.make(), "role": 1},
|
data={"user": subscriber_user.make(), "role": self.member_role.id},
|
||||||
request_user=user,
|
request_user=user,
|
||||||
club=self.club,
|
club=self.club,
|
||||||
)
|
)
|
||||||
assert not form.is_valid()
|
assert not form.is_valid()
|
||||||
assert form.errors == {
|
assert form.errors == {
|
||||||
"role": ["Sélectionnez un choix valide. 1 n\u2019en fait pas partie."]
|
"role": [
|
||||||
|
"Sélectionnez un choix valide. "
|
||||||
|
"Ce choix ne fait pas partie de ceux disponibles."
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_simple_members_dont_see_form_anymore(self):
|
def test_simple_members_dont_see_form_anymore(self):
|
||||||
"""Test that simple club members don't see the form to add members"""
|
"""Test that simple club members don't see the form to add members"""
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
baker.make(Membership, club=self.club, user=user, role=1)
|
baker.make(Membership, club=self.club, user=user, role=self.member_role)
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
res = self.client.get(self.members_url)
|
res = self.client.get(self.members_url)
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
@@ -382,9 +424,10 @@ class TestMembership(TestClub):
|
|||||||
"""Test that board members of the club can end memberships
|
"""Test that board members of the club can end memberships
|
||||||
of users with lower roles.
|
of users with lower roles.
|
||||||
"""
|
"""
|
||||||
# reminder : simple_board_member has role 3
|
|
||||||
self.client.force_login(self.simple_board_member)
|
self.client.force_login(self.simple_board_member)
|
||||||
membership = baker.make(Membership, club=self.club, role=2, end_date=None)
|
role = baker.make(ClubRole, club=self.club, is_board=True)
|
||||||
|
role.below(self.board_role)
|
||||||
|
membership = baker.make(Membership, club=self.club, role=role)
|
||||||
response = self.client.post(self.members_url, {"members_old": [membership.id]})
|
response = self.client.post(self.members_url, {"members_old": [membership.id]})
|
||||||
self.assertRedirects(response, self.members_url)
|
self.assertRedirects(response, self.members_url)
|
||||||
self.club.refresh_from_db()
|
self.club.refresh_from_db()
|
||||||
@@ -394,7 +437,9 @@ class TestMembership(TestClub):
|
|||||||
"""Test that board members of the club cannot end memberships
|
"""Test that board members of the club cannot end memberships
|
||||||
of users with higher roles.
|
of users with higher roles.
|
||||||
"""
|
"""
|
||||||
membership = self.president.memberships.filter(club=self.club).first()
|
membership = self.president.memberships.filter(
|
||||||
|
club=self.club, end_date=None
|
||||||
|
).first()
|
||||||
self.client.force_login(self.simple_board_member)
|
self.client.force_login(self.simple_board_member)
|
||||||
self.client.post(self.members_url, {"members_old": [membership.id]})
|
self.client.post(self.members_url, {"members_old": [membership.id]})
|
||||||
self.club.refresh_from_db()
|
self.club.refresh_from_db()
|
||||||
@@ -436,7 +481,9 @@ class TestMembership(TestClub):
|
|||||||
def test_remove_from_club_group(self):
|
def test_remove_from_club_group(self):
|
||||||
"""Test that when a membership ends, the user is removed from club groups."""
|
"""Test that when a membership ends, the user is removed from club groups."""
|
||||||
user = baker.make(User)
|
user = baker.make(User)
|
||||||
baker.make(Membership, user=user, club=self.club, end_date=None, role=3)
|
baker.make(
|
||||||
|
Membership, user=user, club=self.club, end_date=None, role=self.board_role
|
||||||
|
)
|
||||||
assert user.groups.contains(self.club.members_group)
|
assert user.groups.contains(self.club.members_group)
|
||||||
assert user.groups.contains(self.club.board_group)
|
assert user.groups.contains(self.club.board_group)
|
||||||
user.memberships.update(end_date=localdate())
|
user.memberships.update(end_date=localdate())
|
||||||
@@ -447,18 +494,20 @@ class TestMembership(TestClub):
|
|||||||
"""Test that when a membership begins, the user is added to the club group."""
|
"""Test that when a membership begins, the user is added to the club group."""
|
||||||
assert not self.subscriber.groups.contains(self.club.members_group)
|
assert not self.subscriber.groups.contains(self.club.members_group)
|
||||||
assert not self.subscriber.groups.contains(self.club.board_group)
|
assert not self.subscriber.groups.contains(self.club.board_group)
|
||||||
baker.make(Membership, club=self.club, user=self.subscriber, role=3)
|
baker.make(
|
||||||
|
Membership, club=self.club, user=self.subscriber, role=self.board_role
|
||||||
|
)
|
||||||
assert self.subscriber.groups.contains(self.club.members_group)
|
assert self.subscriber.groups.contains(self.club.members_group)
|
||||||
assert self.subscriber.groups.contains(self.club.board_group)
|
assert self.subscriber.groups.contains(self.club.board_group)
|
||||||
|
|
||||||
def test_change_position_in_club(self):
|
def test_change_position_in_club(self):
|
||||||
"""Test that when moving from board to members, club group change"""
|
"""Test that when moving from board to members, club group change"""
|
||||||
membership = baker.make(
|
membership = baker.make(
|
||||||
Membership, club=self.club, user=self.subscriber, role=3
|
Membership, club=self.club, user=self.subscriber, role=self.board_role
|
||||||
)
|
)
|
||||||
assert self.subscriber.groups.contains(self.club.members_group)
|
assert self.subscriber.groups.contains(self.club.members_group)
|
||||||
assert self.subscriber.groups.contains(self.club.board_group)
|
assert self.subscriber.groups.contains(self.club.board_group)
|
||||||
membership.role = 1
|
membership.role = self.member_role
|
||||||
membership.save()
|
membership.save()
|
||||||
assert self.subscriber.groups.contains(self.club.members_group)
|
assert self.subscriber.groups.contains(self.club.members_group)
|
||||||
assert not self.subscriber.groups.contains(self.club.board_group)
|
assert not self.subscriber.groups.contains(self.club.board_group)
|
||||||
@@ -471,7 +520,11 @@ class TestMembership(TestClub):
|
|||||||
|
|
||||||
# make sli a board member
|
# make sli a board member
|
||||||
self.sli.memberships.all().delete()
|
self.sli.memberships.all().delete()
|
||||||
Membership(club=self.ae, user=self.sli, role=3).save()
|
Membership(
|
||||||
|
club=self.ae,
|
||||||
|
user=self.sli,
|
||||||
|
role=baker.make(ClubRole, club=self.ae, is_board=True),
|
||||||
|
).save()
|
||||||
assert self.club.is_owned_by(self.sli)
|
assert self.club.is_owned_by(self.sli)
|
||||||
|
|
||||||
def test_change_club_name(self):
|
def test_change_club_name(self):
|
||||||
@@ -497,7 +550,7 @@ class TestMembership(TestClub):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_membership_set_old(client: Client):
|
def test_membership_set_old(client: Client):
|
||||||
membership = baker.make(Membership, end_date=None, user=(subscriber_user.make()))
|
membership = baker.make(Membership, end_date=None, user=subscriber_user.make())
|
||||||
client.force_login(membership.user)
|
client.force_login(membership.user)
|
||||||
response = client.post(
|
response = client.post(
|
||||||
reverse("club:membership_set_old", kwargs={"membership_id": membership.id})
|
reverse("club:membership_set_old", kwargs={"membership_id": membership.id})
|
||||||
@@ -531,55 +584,63 @@ class TestJoinClub:
|
|||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("user_factory", "role", "errors"),
|
("user_factory", "board_role", "errors"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
subscriber_user.make,
|
subscriber_user.make,
|
||||||
2,
|
True,
|
||||||
{
|
{
|
||||||
"role": [
|
"role": [
|
||||||
"Sélectionnez un choix valide. 2 n\u2019en fait pas partie."
|
"Sélectionnez un choix valide. "
|
||||||
|
"Ce choix ne fait pas partie de ceux disponibles."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
lambda: baker.make(User),
|
lambda: baker.make(User),
|
||||||
1,
|
False,
|
||||||
{"__all__": ["Vous devez être cotisant pour faire partie d'un club"]},
|
{"__all__": ["Vous devez être cotisant pour faire partie d'un club"]},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_join_club_errors(
|
def test_join_club_errors(
|
||||||
self, user_factory: Callable[[], User], role: int, errors: dict
|
self, user_factory: Callable[[], User], board_role, errors: dict
|
||||||
):
|
):
|
||||||
club = baker.make(Club)
|
club = baker.make(Club)
|
||||||
user = user_factory()
|
user = user_factory()
|
||||||
form = JoinClubForm(club=club, request_user=user, data={"role": role})
|
role = baker.make(ClubRole, club=club, is_board=board_role)
|
||||||
|
form = JoinClubForm(club=club, request_user=user, data={"role": role.id})
|
||||||
assert not form.is_valid()
|
assert not form.is_valid()
|
||||||
assert form.errors == errors
|
assert form.errors == errors
|
||||||
|
|
||||||
def test_user_already_in_club(self):
|
def test_user_already_in_club(self):
|
||||||
club = baker.make(Club)
|
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
baker.make(Membership, user=user, club=club)
|
role = baker.make(ClubRole, is_board=False)
|
||||||
form = JoinClubForm(club=club, request_user=user, data={"role": 1})
|
baker.make(Membership, user=user, club=role.club)
|
||||||
|
form = JoinClubForm(club=role.club, request_user=user, data={"role": role.id})
|
||||||
assert not form.is_valid()
|
assert not form.is_valid()
|
||||||
assert form.errors == {"__all__": ["Vous êtes déjà membre de ce club."]}
|
assert form.errors == {"__all__": ["Vous êtes déjà membre de ce club."]}
|
||||||
|
|
||||||
def test_ok(self):
|
def test_ok(self):
|
||||||
club = baker.make(Club)
|
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
form = JoinClubForm(club=club, request_user=user, data={"role": 1})
|
role = baker.make(ClubRole, is_board=False)
|
||||||
|
form = JoinClubForm(club=role.club, request_user=user, data={"role": role.id})
|
||||||
assert form.is_valid()
|
assert form.is_valid()
|
||||||
form.save()
|
form.save()
|
||||||
assert Membership.objects.ongoing().filter(user=user, club=club).exists()
|
assert Membership.objects.ongoing().filter(user=user, club=role.club).exists()
|
||||||
|
|
||||||
|
|
||||||
class TestOldMembersView(TestCase):
|
class TestOldMembersView(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
club = baker.make(Club)
|
club = baker.make(Club)
|
||||||
roles = [1, 1, 1, 2, 2, 4, 4, 5, 7, 9, 10]
|
roles = baker.make(
|
||||||
|
ClubRole,
|
||||||
|
club=club,
|
||||||
|
is_board=itertools.cycle([True, True, False]),
|
||||||
|
_quantity=10,
|
||||||
|
_bulk_create=True,
|
||||||
|
)
|
||||||
cls.memberships = baker.make(
|
cls.memberships = baker.make(
|
||||||
Membership,
|
Membership,
|
||||||
role=iter(roles),
|
role=iter(roles),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from django.urls import reverse
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from pytest_django.asserts import assertHTMLEqual, assertRedirects
|
from pytest_django.asserts import assertHTMLEqual, assertRedirects
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
from core.markdown import markdown
|
from core.markdown import markdown
|
||||||
from core.models import PageRev, User
|
from core.models import PageRev, User
|
||||||
@@ -59,7 +59,12 @@ def test_page_revision(client: Client):
|
|||||||
def test_edit_page(client: Client):
|
def test_edit_page(client: Client):
|
||||||
club = baker.make(Club)
|
club = baker.make(Club)
|
||||||
user = subscriber_user.make()
|
user = subscriber_user.make()
|
||||||
baker.make(Membership, user=user, club=club, role=3)
|
baker.make(
|
||||||
|
Membership,
|
||||||
|
user=user,
|
||||||
|
club=club,
|
||||||
|
role=baker.make(ClubRole, club=club, is_board=True),
|
||||||
|
)
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
url = reverse("club:club_edit_page", kwargs={"club_id": club.id})
|
url = reverse("club:club_edit_page", kwargs={"club_id": club.id})
|
||||||
content = "# foo\nLorem ipsum dolor sit amet"
|
content = "# foo\nLorem ipsum dolor sit amet"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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 club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from club.schemas import UserMembershipSchema
|
from club.schemas import UserMembershipSchema
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
from core.models import Page
|
from core.models import Page
|
||||||
@@ -19,7 +19,10 @@ class TestFetchClub(TestCase):
|
|||||||
pages = baker.make(Page, _quantity=3, _bulk_create=True)
|
pages = baker.make(Page, _quantity=3, _bulk_create=True)
|
||||||
clubs = baker.make(Club, page=iter(pages), _quantity=3, _bulk_create=True)
|
clubs = baker.make(Club, page=iter(pages), _quantity=3, _bulk_create=True)
|
||||||
recipe = Recipe(
|
recipe = Recipe(
|
||||||
Membership, user=cls.user, start_date=localdate() - timedelta(days=2)
|
Membership,
|
||||||
|
user=cls.user,
|
||||||
|
start_date=localdate() - timedelta(days=2),
|
||||||
|
role=baker.make(ClubRole),
|
||||||
)
|
)
|
||||||
cls.members = Membership.objects.bulk_create(
|
cls.members = Membership.objects.bulk_create(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from django.utils.translation import gettext as _
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from pytest_django.asserts import assertNumQueries, assertRedirects
|
from pytest_django.asserts import assertNumQueries, assertRedirects
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle
|
from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle
|
||||||
from core.baker_recipes import subscriber_user
|
from core.baker_recipes import subscriber_user
|
||||||
from core.models import AnonymousUser, Group, User
|
from core.models import AnonymousUser, Group, User
|
||||||
@@ -214,7 +214,8 @@ class TestNewsCreation(TestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.club = baker.make(Club)
|
cls.club = baker.make(Club)
|
||||||
cls.user = subscriber_user.make()
|
cls.user = subscriber_user.make()
|
||||||
baker.make(Membership, user=cls.user, club=cls.club, role=5)
|
role = baker.make(ClubRole, club=cls.club, is_board=True)
|
||||||
|
baker.make(Membership, user=cls.user, club=cls.club, role=role)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ class WeekmailArticleCreateView(CreateView):
|
|||||||
self.object = form.instance
|
self.object = form.instance
|
||||||
form.is_valid() # Valid a first time to populate club field
|
form.is_valid() # Valid a first time to populate club field
|
||||||
m = form.instance.club.get_membership_for(request.user)
|
m = form.instance.club.get_membership_for(request.user)
|
||||||
if m is None or m.role <= settings.SITH_MAXIMUM_FREE_ROLE:
|
if m is None or not m.role.is_board:
|
||||||
form.add_error(
|
form.add_error(
|
||||||
"club",
|
"club",
|
||||||
ValidationError(
|
ValidationError(
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.timezone import localdate, now
|
from django.utils.timezone import localdate, now
|
||||||
from model_bakery import seq
|
from model_bakery import seq
|
||||||
from model_bakery.recipe import Recipe, related
|
from model_bakery.recipe import Recipe, foreign_key, related
|
||||||
|
|
||||||
from club.models import Membership
|
from club.models import ClubRole, Membership
|
||||||
from core.models import Group, User
|
from core.models import Group, User
|
||||||
from subscription.models import Subscription
|
from subscription.models import Subscription
|
||||||
|
|
||||||
@@ -52,7 +52,9 @@ ae_board_membership = Recipe(
|
|||||||
Membership,
|
Membership,
|
||||||
start_date=now() - timedelta(days=30),
|
start_date=now() - timedelta(days=30),
|
||||||
club_id=settings.SITH_MAIN_CLUB_ID,
|
club_id=settings.SITH_MAIN_CLUB_ID,
|
||||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
role=foreign_key(
|
||||||
|
Recipe(ClubRole, club_id=settings.SITH_MAIN_CLUB_ID, is_board=True)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
board_user = Recipe(
|
board_user = Recipe(
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from django.utils import timezone
|
|||||||
from django.utils.timezone import localdate
|
from django.utils.timezone import localdate
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from com.ics_calendar import IcsCalendar
|
from com.ics_calendar import IcsCalendar
|
||||||
from com.models import News, NewsDate, Sith, Weekmail
|
from com.models import News, NewsDate, Sith, Weekmail
|
||||||
from core.models import BanGroup, Group, Page, PageRev, SithFile, User
|
from core.models import BanGroup, Group, Page, PageRev, SithFile, User
|
||||||
@@ -62,6 +62,13 @@ class PopulatedGroups(NamedTuple):
|
|||||||
campus_admin: Group
|
campus_admin: Group
|
||||||
|
|
||||||
|
|
||||||
|
class PopulatedClubs(NamedTuple):
|
||||||
|
ae: Club
|
||||||
|
troll: Club
|
||||||
|
pdf: Club
|
||||||
|
refound: Club
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
ROOT_PATH: ClassVar[Path] = Path(__file__).parent.parent.parent.parent
|
ROOT_PATH: ClassVar[Path] = Path(__file__).parent.parent.parent.parent
|
||||||
SAS_FIXTURE_PATH: ClassVar[Path] = (
|
SAS_FIXTURE_PATH: ClassVar[Path] = (
|
||||||
@@ -111,28 +118,16 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
club_root = SithFile.objects.create(name="clubs", owner=root)
|
club_root = SithFile.objects.create(name="clubs", owner=root)
|
||||||
sas = SithFile.objects.create(name="SAS", owner=root)
|
sas = SithFile.objects.create(name="SAS", owner=root)
|
||||||
main_club = Club.objects.create(
|
clubs = self._create_clubs()
|
||||||
id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort"
|
|
||||||
)
|
|
||||||
main_club.board_group.permissions.add(
|
|
||||||
*Permission.objects.filter(
|
|
||||||
codename__in=["view_subscription", "add_subscription"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
bar_club = Club.objects.create(
|
|
||||||
id=settings.SITH_PDF_CLUB_ID,
|
|
||||||
name="PdF",
|
|
||||||
address="6 Boulevard Anatole France, 90000 Belfort",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.reset_index("club")
|
self.reset_index("club")
|
||||||
for bar_id, bar_name in settings.SITH_COUNTER_BARS:
|
for bar_id, bar_name in settings.SITH_COUNTER_BARS:
|
||||||
Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR").save()
|
Counter(id=bar_id, name=bar_name, club=clubs.pdf, type="BAR").save()
|
||||||
self.reset_index("counter")
|
self.reset_index("counter")
|
||||||
counters = [
|
counters = [
|
||||||
Counter(name="Eboutic", club=main_club, type="EBOUTIC"),
|
Counter(name="Eboutic", club=clubs.ae, type="EBOUTIC"),
|
||||||
Counter(name="AE", club=main_club, type="OFFICE"),
|
Counter(name="AE", club=clubs.ae, type="OFFICE"),
|
||||||
Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"),
|
Counter(name="Vidage comptes AE", club=clubs.ae, type="OFFICE"),
|
||||||
]
|
]
|
||||||
Counter.objects.bulk_create(counters)
|
Counter.objects.bulk_create(counters)
|
||||||
bar_groups = []
|
bar_groups = []
|
||||||
@@ -315,54 +310,41 @@ class Command(BaseCommand):
|
|||||||
self._create_subscription(tutu)
|
self._create_subscription(tutu)
|
||||||
StudentCard(uid="9A89B82018B0A0", customer=sli.customer).save()
|
StudentCard(uid="9A89B82018B0A0", customer=sli.customer).save()
|
||||||
|
|
||||||
# Clubs
|
Membership.objects.create(
|
||||||
Club.objects.create(
|
user=skia, club=clubs.ae, role=clubs.ae.roles.get(name="Respo Info")
|
||||||
name="Bibo'UT", address="46 de la Boustifaille", parent=main_club
|
|
||||||
)
|
)
|
||||||
guyut = Club.objects.create(
|
|
||||||
name="Guy'UT", address="42 de la Boustifaille", parent=main_club
|
|
||||||
)
|
|
||||||
Club.objects.create(name="Woenzel'UT", address="Woenzel", parent=guyut)
|
|
||||||
troll = Club.objects.create(
|
|
||||||
name="Troll Penché", address="Terre Du Milieu", parent=main_club
|
|
||||||
)
|
|
||||||
refound = Club.objects.create(
|
|
||||||
name="Carte AE", address="Jamais imprimée", parent=main_club
|
|
||||||
)
|
|
||||||
|
|
||||||
Membership.objects.create(user=skia, club=main_club, role=3)
|
|
||||||
Membership.objects.create(
|
Membership.objects.create(
|
||||||
user=comunity,
|
user=comunity,
|
||||||
club=bar_club,
|
club=clubs.pdf,
|
||||||
start_date=localdate(),
|
start_date=localdate(),
|
||||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
role=clubs.pdf.roles.get(name="Membre du bureau"),
|
||||||
)
|
)
|
||||||
Membership.objects.create(
|
Membership.objects.create(
|
||||||
user=sli,
|
user=sli,
|
||||||
club=troll,
|
club=clubs.troll,
|
||||||
role=9,
|
role=clubs.troll.roles.get(name="Vice-Président⸱e"),
|
||||||
description="Padawan Troll",
|
description="Padawan Troll",
|
||||||
start_date=localdate() - timedelta(days=17),
|
start_date=localdate() - timedelta(days=17),
|
||||||
)
|
)
|
||||||
Membership.objects.create(
|
Membership.objects.create(
|
||||||
user=krophil,
|
user=krophil,
|
||||||
club=troll,
|
club=clubs.troll,
|
||||||
role=10,
|
role=clubs.troll.roles.get(name="Président⸱e"),
|
||||||
description="Maitre Troll",
|
description="Maitre Troll",
|
||||||
start_date=localdate() - timedelta(days=200),
|
start_date=localdate() - timedelta(days=200),
|
||||||
)
|
)
|
||||||
Membership.objects.create(
|
Membership.objects.create(
|
||||||
user=skia,
|
user=skia,
|
||||||
club=troll,
|
club=clubs.troll,
|
||||||
role=2,
|
role=clubs.troll.roles.get(name="Membre du bureau"),
|
||||||
description="Grand Ancien Troll",
|
description="Grand Ancien Troll",
|
||||||
start_date=localdate() - timedelta(days=400),
|
start_date=localdate() - timedelta(days=400),
|
||||||
end_date=localdate() - timedelta(days=86),
|
end_date=localdate() - timedelta(days=86),
|
||||||
)
|
)
|
||||||
Membership.objects.create(
|
Membership.objects.create(
|
||||||
user=richard,
|
user=richard,
|
||||||
club=troll,
|
club=clubs.troll,
|
||||||
role=2,
|
role=clubs.troll.roles.get(name="Membre du bureau"),
|
||||||
description="",
|
description="",
|
||||||
start_date=localdate() - timedelta(days=200),
|
start_date=localdate() - timedelta(days=200),
|
||||||
end_date=localdate() - timedelta(days=100),
|
end_date=localdate() - timedelta(days=100),
|
||||||
@@ -379,7 +361,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="15",
|
purchase_price="15",
|
||||||
selling_price="15",
|
selling_price="15",
|
||||||
special_selling_price="15",
|
special_selling_price="15",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
)
|
)
|
||||||
cotis2 = Product.objects.create(
|
cotis2 = Product.objects.create(
|
||||||
name="Cotis 2 semestres",
|
name="Cotis 2 semestres",
|
||||||
@@ -388,7 +370,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="28",
|
purchase_price="28",
|
||||||
selling_price="28",
|
selling_price="28",
|
||||||
special_selling_price="28",
|
special_selling_price="28",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
)
|
)
|
||||||
refill = Product.objects.create(
|
refill = Product.objects.create(
|
||||||
name="Rechargement 15 €",
|
name="Rechargement 15 €",
|
||||||
@@ -397,7 +379,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="15",
|
purchase_price="15",
|
||||||
selling_price="15",
|
selling_price="15",
|
||||||
special_selling_price="15",
|
special_selling_price="15",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
)
|
)
|
||||||
barb = Product.objects.create(
|
barb = Product.objects.create(
|
||||||
name="Barbar",
|
name="Barbar",
|
||||||
@@ -406,7 +388,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="1.50",
|
purchase_price="1.50",
|
||||||
selling_price="1.7",
|
selling_price="1.7",
|
||||||
special_selling_price="1.6",
|
special_selling_price="1.6",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
limit_age=18,
|
limit_age=18,
|
||||||
)
|
)
|
||||||
cble = Product.objects.create(
|
cble = Product.objects.create(
|
||||||
@@ -416,7 +398,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="1.50",
|
purchase_price="1.50",
|
||||||
selling_price="1.7",
|
selling_price="1.7",
|
||||||
special_selling_price="1.6",
|
special_selling_price="1.6",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
limit_age=18,
|
limit_age=18,
|
||||||
)
|
)
|
||||||
cons = Product.objects.create(
|
cons = Product.objects.create(
|
||||||
@@ -426,7 +408,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="1",
|
purchase_price="1",
|
||||||
selling_price="1",
|
selling_price="1",
|
||||||
special_selling_price="1",
|
special_selling_price="1",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
)
|
)
|
||||||
dcons = Product.objects.create(
|
dcons = Product.objects.create(
|
||||||
name="Déconsigne Eco-cup",
|
name="Déconsigne Eco-cup",
|
||||||
@@ -435,7 +417,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="-1",
|
purchase_price="-1",
|
||||||
selling_price="-1",
|
selling_price="-1",
|
||||||
special_selling_price="-1",
|
special_selling_price="-1",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
)
|
)
|
||||||
cors = Product.objects.create(
|
cors = Product.objects.create(
|
||||||
name="Corsendonk",
|
name="Corsendonk",
|
||||||
@@ -444,7 +426,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="1.50",
|
purchase_price="1.50",
|
||||||
selling_price="1.7",
|
selling_price="1.7",
|
||||||
special_selling_price="1.6",
|
special_selling_price="1.6",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
limit_age=18,
|
limit_age=18,
|
||||||
)
|
)
|
||||||
carolus = Product.objects.create(
|
carolus = Product.objects.create(
|
||||||
@@ -454,7 +436,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="1.50",
|
purchase_price="1.50",
|
||||||
selling_price="1.7",
|
selling_price="1.7",
|
||||||
special_selling_price="1.6",
|
special_selling_price="1.6",
|
||||||
club=main_club,
|
club=clubs.ae,
|
||||||
limit_age=18,
|
limit_age=18,
|
||||||
)
|
)
|
||||||
Product.objects.create(
|
Product.objects.create(
|
||||||
@@ -463,7 +445,7 @@ class Command(BaseCommand):
|
|||||||
purchase_price="0",
|
purchase_price="0",
|
||||||
selling_price="0",
|
selling_price="0",
|
||||||
special_selling_price="0",
|
special_selling_price="0",
|
||||||
club=refound,
|
club=clubs.refound,
|
||||||
)
|
)
|
||||||
groups.subscribers.products.add(
|
groups.subscribers.products.add(
|
||||||
cotis, cotis2, refill, barb, cble, cors, carolus
|
cotis, cotis2, refill, barb, cble, cors, carolus
|
||||||
@@ -476,7 +458,7 @@ class Command(BaseCommand):
|
|||||||
eboutic = Counter.objects.get(name="Eboutic")
|
eboutic = Counter.objects.get(name="Eboutic")
|
||||||
eboutic.products.add(barb, cotis, cotis2, refill)
|
eboutic.products.add(barb, cotis, cotis2, refill)
|
||||||
|
|
||||||
Counter.objects.create(name="Carte AE", club=refound, type="OFFICE")
|
Counter.objects.create(name="Carte AE", club=clubs.refound, type="OFFICE")
|
||||||
|
|
||||||
ReturnableProduct.objects.create(
|
ReturnableProduct.objects.create(
|
||||||
product=cons, returned_product=dcons, max_return=3
|
product=cons, returned_product=dcons, max_return=3
|
||||||
@@ -500,7 +482,7 @@ class Command(BaseCommand):
|
|||||||
end_date="7942-06-12 10:28:45+01",
|
end_date="7942-06-12 10:28:45+01",
|
||||||
)
|
)
|
||||||
el.view_groups.add(groups.public)
|
el.view_groups.add(groups.public)
|
||||||
el.edit_groups.add(main_club.board_group)
|
el.edit_groups.add(clubs.ae.board_group)
|
||||||
el.candidature_groups.add(groups.subscribers)
|
el.candidature_groups.add(groups.subscribers)
|
||||||
el.vote_groups.add(groups.subscribers)
|
el.vote_groups.add(groups.subscribers)
|
||||||
liste = ElectionList.objects.create(title="Candidature Libre", election=el)
|
liste = ElectionList.objects.create(title="Candidature Libre", election=el)
|
||||||
@@ -573,7 +555,7 @@ class Command(BaseCommand):
|
|||||||
title="Apero barman",
|
title="Apero barman",
|
||||||
summary="Viens boire un coup avec les barmans",
|
summary="Viens boire un coup avec les barmans",
|
||||||
content="Glou glou glou glou glou glou glou",
|
content="Glou glou glou glou glou glou glou",
|
||||||
club=bar_club,
|
club=clubs.pdf,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
is_published=True,
|
is_published=True,
|
||||||
moderator=skia,
|
moderator=skia,
|
||||||
@@ -591,7 +573,7 @@ class Command(BaseCommand):
|
|||||||
content=(
|
content=(
|
||||||
"Viens donc t'enjailler avec les autres barmans aux frais du BdF! \\o/"
|
"Viens donc t'enjailler avec les autres barmans aux frais du BdF! \\o/"
|
||||||
),
|
),
|
||||||
club=bar_club,
|
club=clubs.pdf,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
is_published=True,
|
is_published=True,
|
||||||
moderator=skia,
|
moderator=skia,
|
||||||
@@ -607,7 +589,7 @@ class Command(BaseCommand):
|
|||||||
title="Repas fromager",
|
title="Repas fromager",
|
||||||
summary="Wien manger du l'bon fromeug'",
|
summary="Wien manger du l'bon fromeug'",
|
||||||
content="Fô viendre mangey d'la bonne fondue!",
|
content="Fô viendre mangey d'la bonne fondue!",
|
||||||
club=bar_club,
|
club=clubs.pdf,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
is_published=True,
|
is_published=True,
|
||||||
moderator=skia,
|
moderator=skia,
|
||||||
@@ -623,7 +605,7 @@ class Command(BaseCommand):
|
|||||||
title="SdF",
|
title="SdF",
|
||||||
summary="Enjoy la fin des finaux!",
|
summary="Enjoy la fin des finaux!",
|
||||||
content="Viens faire la fête avec tout plein de gens!",
|
content="Viens faire la fête avec tout plein de gens!",
|
||||||
club=bar_club,
|
club=clubs.pdf,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
is_published=True,
|
is_published=True,
|
||||||
moderator=skia,
|
moderator=skia,
|
||||||
@@ -641,7 +623,7 @@ class Command(BaseCommand):
|
|||||||
summary="Viens jouer!",
|
summary="Viens jouer!",
|
||||||
content="Rejoins la fine équipe du Troll Penché et viens "
|
content="Rejoins la fine équipe du Troll Penché et viens "
|
||||||
"t'amuser le Vendredi soir!",
|
"t'amuser le Vendredi soir!",
|
||||||
club=troll,
|
club=clubs.troll,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
is_published=True,
|
is_published=True,
|
||||||
moderator=skia,
|
moderator=skia,
|
||||||
@@ -778,6 +760,52 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
s.save()
|
s.save()
|
||||||
|
|
||||||
|
def _create_clubs(self) -> PopulatedClubs:
|
||||||
|
ae = Club.objects.create(
|
||||||
|
id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort"
|
||||||
|
)
|
||||||
|
ae.board_group.permissions.add(
|
||||||
|
*Permission.objects.filter(
|
||||||
|
codename__in=["view_subscription", "add_subscription", "add_membership"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pdf = Club.objects.create(
|
||||||
|
id=settings.SITH_PDF_CLUB_ID,
|
||||||
|
name="PdF",
|
||||||
|
address="6 Boulevard Anatole France, 90000 Belfort",
|
||||||
|
)
|
||||||
|
troll = Club.objects.create(
|
||||||
|
name="Troll Penché", address="Terre Du Milieu", parent=ae
|
||||||
|
)
|
||||||
|
refound = Club.objects.create(
|
||||||
|
name="Carte AE", address="Jamais imprimée", parent=ae
|
||||||
|
)
|
||||||
|
roles = []
|
||||||
|
presidency_roles = ["Président⸱e", "Vice-Président⸱e"]
|
||||||
|
board_roles = [
|
||||||
|
"Trésorier⸱e",
|
||||||
|
"Secrétaire",
|
||||||
|
"Respo Info",
|
||||||
|
"Respo Com",
|
||||||
|
"Membre du bureau",
|
||||||
|
]
|
||||||
|
simple_roles = ["Membre actif⸱ve", "Curieux⸱euse"]
|
||||||
|
for club in ae, pdf, troll, refound:
|
||||||
|
for i, role in enumerate(presidency_roles):
|
||||||
|
roles.append(
|
||||||
|
ClubRole(
|
||||||
|
club=club, order=i, name=role, is_presidency=True, is_board=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for i, role in enumerate(board_roles, start=len(presidency_roles)):
|
||||||
|
roles.append(ClubRole(club=club, order=i, name=role, is_board=True))
|
||||||
|
for i, role in enumerate(
|
||||||
|
simple_roles, start=len(presidency_roles) + len(board_roles)
|
||||||
|
):
|
||||||
|
roles.append(ClubRole(club=club, order=i, name=role))
|
||||||
|
ClubRole.objects.bulk_create(roles)
|
||||||
|
return PopulatedClubs(ae=ae, troll=troll, pdf=pdf, refound=refound)
|
||||||
|
|
||||||
def _create_groups(self) -> PopulatedGroups:
|
def _create_groups(self) -> PopulatedGroups:
|
||||||
perms = Permission.objects.all()
|
perms = Permission.objects.all()
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from django.db.models import Count, Exists, Min, OuterRef, Subquery
|
|||||||
from django.utils.timezone import localdate, make_aware, now
|
from django.utils.timezone import localdate, make_aware, now
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.models import Group, User, UserBan
|
from core.models import Group, User, UserBan
|
||||||
from counter.models import (
|
from counter.models import (
|
||||||
Counter,
|
Counter,
|
||||||
@@ -172,20 +172,25 @@ class Command(BaseCommand):
|
|||||||
Customer.objects.bulk_create(customers, ignore_conflicts=True)
|
Customer.objects.bulk_create(customers, ignore_conflicts=True)
|
||||||
|
|
||||||
def make_club(self, club: Club, members: list[User], old_members: list[User]):
|
def make_club(self, club: Club, members: list[User], old_members: list[User]):
|
||||||
def zip_roles(users: list[User]) -> Iterator[tuple[User, int]]:
|
roles: list[ClubRole] = list(club.roles.all())
|
||||||
roles = iter(sorted(settings.SITH_CLUB_ROLES.keys(), reverse=True))
|
|
||||||
|
def zip_roles(users: list[User]) -> Iterator[tuple[User, ClubRole]]:
|
||||||
|
important_roles = [r for r in roles if r.is_board]
|
||||||
|
important_roles.sort(key=lambda r: r.order)
|
||||||
|
simple_board_role = important_roles.pop()
|
||||||
|
member_roles = [r for r in roles if not r.is_board]
|
||||||
user_idx = 0
|
user_idx = 0
|
||||||
while (role := next(roles)) > 2:
|
for _role in important_roles:
|
||||||
# one member for each major role
|
# one member for each major role
|
||||||
yield users[user_idx], role
|
yield users[user_idx], _role
|
||||||
user_idx += 1
|
user_idx += 1
|
||||||
for _ in range(int(0.3 * (len(users) - user_idx))):
|
for _ in range(int(0.3 * (len(users) - user_idx))):
|
||||||
# 30% of the remaining in the board
|
# 30% of the remaining in the board
|
||||||
yield users[user_idx], 2
|
yield users[user_idx], simple_board_role
|
||||||
user_idx += 1
|
user_idx += 1
|
||||||
for remaining in users[user_idx + 1 :]:
|
for remaining in users[user_idx + 1 :]:
|
||||||
# everything else is a simple member
|
# everything else is a simple member
|
||||||
yield remaining, 1
|
yield remaining, random.choices(member_roles, weights=(0.8, 0.2))[0]
|
||||||
|
|
||||||
memberships = []
|
memberships = []
|
||||||
old_members = old_members.copy()
|
old_members = old_members.copy()
|
||||||
@@ -197,19 +202,14 @@ class Command(BaseCommand):
|
|||||||
start_date=start,
|
start_date=start,
|
||||||
end_date=self.faker.past_date(start),
|
end_date=self.faker.past_date(start),
|
||||||
user=old,
|
user=old,
|
||||||
role=random.choice(list(settings.SITH_CLUB_ROLES.keys())),
|
role=random.choice(roles),
|
||||||
club=club,
|
club=club,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for member, role in zip_roles(members):
|
for member, role in zip_roles(members):
|
||||||
start = self.faker.past_date("-1y")
|
start = self.faker.past_date("-1y")
|
||||||
memberships.append(
|
memberships.append(
|
||||||
Membership(
|
Membership(start_date=start, user=member, role=role, club=club)
|
||||||
start_date=start,
|
|
||||||
user=member,
|
|
||||||
role=role,
|
|
||||||
club=club,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
memberships = Membership.objects.bulk_create(memberships)
|
memberships = Membership.objects.bulk_create(memberships)
|
||||||
Membership._add_club_groups(memberships)
|
Membership._add_club_groups(memberships)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from django.utils.timezone import now
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from pytest_django.asserts import assertHTMLEqual, assertRedirects
|
from pytest_django.asserts import assertHTMLEqual, assertRedirects
|
||||||
|
|
||||||
from club.models import Club
|
from club.models import Club, Membership
|
||||||
from core.baker_recipes import board_user, subscriber_user
|
from core.baker_recipes import board_user, subscriber_user
|
||||||
from core.markdown import markdown
|
from core.markdown import markdown
|
||||||
from core.models import AnonymousUser, Page, PageRev, User
|
from core.models import AnonymousUser, Page, PageRev, User
|
||||||
@@ -122,6 +122,9 @@ def test_page_revision_club_redirection(client: Client):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_viewable_by():
|
def test_viewable_by():
|
||||||
# remove existing pages to prevent side effect
|
# remove existing pages to prevent side effect
|
||||||
|
# club pages are protected, so we must delete clubs first
|
||||||
|
Membership.objects.all().delete()
|
||||||
|
Club.objects.all().delete()
|
||||||
Page.objects.all().delete()
|
Page.objects.all().delete()
|
||||||
view_groups = [
|
view_groups = [
|
||||||
[settings.SITH_GROUP_PUBLIC_ID],
|
[settings.SITH_GROUP_PUBLIC_ID],
|
||||||
|
|||||||
@@ -248,14 +248,15 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
"name": _("Groups"),
|
"name": _("Groups"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (
|
can_view_account = (
|
||||||
hasattr(user, "customer")
|
hasattr(user, "customer")
|
||||||
and user.customer
|
and user.customer
|
||||||
and (
|
and (
|
||||||
user == self.request.user
|
user == self.request.user
|
||||||
or self.request.user.has_perm("counter.view_customer")
|
or self.request.user.has_perm("counter.view_customer")
|
||||||
)
|
)
|
||||||
):
|
)
|
||||||
|
if can_view_account or user.preferences.show_my_stats:
|
||||||
tab_list.append(
|
tab_list.append(
|
||||||
{
|
{
|
||||||
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
||||||
@@ -263,6 +264,7 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
"name": _("Stats"),
|
"name": _("Stats"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if can_view_account:
|
||||||
tab_list.append(
|
tab_list.append(
|
||||||
{
|
{
|
||||||
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
|
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
|
||||||
@@ -349,7 +351,7 @@ class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
|
class UserStatsView(UserTabsMixin, UserPassesTestMixin, DetailView):
|
||||||
"""Display a user's stats."""
|
"""Display a user's stats."""
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
@@ -357,15 +359,20 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
|
|||||||
context_object_name = "profile"
|
context_object_name = "profile"
|
||||||
template_name = "core/user_stats.jinja"
|
template_name = "core/user_stats.jinja"
|
||||||
current_tab = "stats"
|
current_tab = "stats"
|
||||||
queryset = User.objects.exclude(customer=None).select_related("customer")
|
queryset = User.objects.exclude(customer=None).select_related(
|
||||||
|
"customer", "_preferences"
|
||||||
|
)
|
||||||
|
|
||||||
def dispatch(self, request, *arg, **kwargs):
|
def test_func(self):
|
||||||
profile = self.get_object()
|
profile: User = self.get_object()
|
||||||
if not (
|
return (
|
||||||
profile == request.user or request.user.has_perm("counter.view_customer")
|
profile == self.request.user
|
||||||
):
|
or self.request.user.has_perm("counter.view_customer")
|
||||||
raise PermissionDenied
|
or (
|
||||||
return super().dispatch(request, *arg, **kwargs)
|
self.request.user.can_view(profile)
|
||||||
|
and profile.preferences.show_my_stats
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from model_bakery import baker
|
|||||||
from model_bakery.recipe import Recipe
|
from model_bakery.recipe import Recipe
|
||||||
from pytest_django.asserts import assertRedirects
|
from pytest_django.asserts import assertRedirects
|
||||||
|
|
||||||
from club.models import Membership
|
from club.models import ClubRole, Membership
|
||||||
from core.baker_recipes import board_user, subscriber_user, very_old_subscriber_user
|
from core.baker_recipes import board_user, subscriber_user, very_old_subscriber_user
|
||||||
from core.models import BanGroup, User
|
from core.models import BanGroup, User
|
||||||
from counter.baker_recipes import product_recipe, sale_recipe
|
from counter.baker_recipes import product_recipe, sale_recipe
|
||||||
@@ -88,7 +88,7 @@ class TestFullClickBase(TestCase):
|
|||||||
Membership,
|
Membership,
|
||||||
start_date=now() - timedelta(days=30),
|
start_date=now() - timedelta(days=30),
|
||||||
club=cls.club_counter.club,
|
club=cls.club_counter.club,
|
||||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
role=baker.make(ClubRole, club=cls.club_counter.club, is_board=True),
|
||||||
user=cls.club_admin,
|
user=cls.club_admin,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -782,7 +782,13 @@ class TestClubCounterClickAccess(TestCase):
|
|||||||
"counter:click",
|
"counter:click",
|
||||||
kwargs={"counter_id": cls.counter.id, "user_id": cls.customer.id},
|
kwargs={"counter_id": cls.counter.id, "user_id": cls.customer.id},
|
||||||
)
|
)
|
||||||
|
cls.board_role, cls.member_role = baker.make(
|
||||||
|
ClubRole,
|
||||||
|
club=cls.counter.club,
|
||||||
|
is_board=iter([True, False]),
|
||||||
|
_quantity=2,
|
||||||
|
_bulk_create=True,
|
||||||
|
)
|
||||||
cls.user = subscriber_user.make()
|
cls.user = subscriber_user.make()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -797,13 +803,17 @@ class TestClubCounterClickAccess(TestCase):
|
|||||||
res = self.client.get(self.click_url)
|
res = self.client.get(self.click_url)
|
||||||
assert res.status_code == 403
|
assert res.status_code == 403
|
||||||
# being a member of the club, without being in the board, isn't enough
|
# being a member of the club, without being in the board, isn't enough
|
||||||
baker.make(Membership, club=self.counter.club, user=self.user, role=1)
|
baker.make(
|
||||||
|
Membership, club=self.counter.club, user=self.user, role=self.member_role
|
||||||
|
)
|
||||||
res = self.client.get(self.click_url)
|
res = self.client.get(self.click_url)
|
||||||
assert res.status_code == 403
|
assert res.status_code == 403
|
||||||
|
|
||||||
def test_board_member(self):
|
def test_board_member(self):
|
||||||
"""By default, board members should be able to click on office counters"""
|
"""By default, board members should be able to click on office counters"""
|
||||||
baker.make(Membership, club=self.counter.club, user=self.user, role=3)
|
baker.make(
|
||||||
|
Membership, club=self.counter.club, user=self.user, role=self.board_role
|
||||||
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
res = self.client.get(self.click_url)
|
res = self.client.get(self.click_url)
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
@@ -818,7 +828,9 @@ class TestClubCounterClickAccess(TestCase):
|
|||||||
def test_both_barman_and_board_member(self):
|
def test_both_barman_and_board_member(self):
|
||||||
"""If the user is barman and board member, he should be authorized as well."""
|
"""If the user is barman and board member, he should be authorized as well."""
|
||||||
self.counter.sellers.add(self.user)
|
self.counter.sellers.add(self.user)
|
||||||
baker.make(Membership, club=self.counter.club, user=self.user, role=3)
|
baker.make(
|
||||||
|
Membership, club=self.counter.club, user=self.user, role=self.board_role
|
||||||
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
res = self.client.get(self.click_url)
|
res = self.client.get(self.click_url)
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ import string
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.base_user import make_password
|
from django.contrib.auth.base_user import make_password
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from club.models import Membership
|
from club.models import ClubRole, Membership
|
||||||
from core.baker_recipes import board_user, subscriber_user
|
from core.baker_recipes import board_user, subscriber_user
|
||||||
from core.models import User
|
from core.models import User
|
||||||
from counter.baker_recipes import product_recipe, refill_recipe, sale_recipe
|
from counter.baker_recipes import product_recipe, refill_recipe, sale_recipe
|
||||||
@@ -42,11 +41,12 @@ class TestStudentCard(TestCase):
|
|||||||
cls.counter.sellers.add(cls.barmen)
|
cls.counter.sellers.add(cls.barmen)
|
||||||
|
|
||||||
cls.club_counter = baker.make(Counter)
|
cls.club_counter = baker.make(Counter)
|
||||||
|
role = baker.make(ClubRole, club=cls.club_counter.club, is_board=True)
|
||||||
baker.make(
|
baker.make(
|
||||||
Membership,
|
Membership,
|
||||||
start_date=now() - timedelta(days=30),
|
start_date=now() - timedelta(days=30),
|
||||||
club=cls.club_counter.club,
|
club=cls.club_counter.club,
|
||||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
role=role,
|
||||||
user=cls.club_admin,
|
user=cls.club_admin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -68,12 +68,6 @@
|
|||||||
<template x-for="[key, value] in Object.entries(data)" :key="key">
|
<template x-for="[key, value] in Object.entries(data)" :key="key">
|
||||||
<input type="hidden" :name="key" :value="value">
|
<input type="hidden" :name="key" :value="value">
|
||||||
</template>
|
</template>
|
||||||
<input type="checkbox" value="cgv" required>
|
|
||||||
<label>
|
|
||||||
{% trans trimmed %}I have read and accept {% endtrans %}
|
|
||||||
<a href="">{% trans %}the general terms and conditions{% endtrans%}</a>
|
|
||||||
{%trans%}of the student assosiation of UTBM{% endtrans %}</label>
|
|
||||||
<br>
|
|
||||||
<input
|
<input
|
||||||
x-cloak
|
x-cloak
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -99,12 +93,6 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
|
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="checkbox" value="cgv" required>
|
|
||||||
<label>
|
|
||||||
{% trans trimmed %}I have read and accept {% endtrans %}
|
|
||||||
<a href="">{% trans %}the general terms and conditions{% endtrans%}</a>
|
|
||||||
{%trans%}of the student assosiation of UTBM{% endtrans %}</label>
|
|
||||||
<br>
|
|
||||||
<input class="btn btn-blue" type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
|
<input class="btn btn-blue" type="submit" value="{% trans %}Pay with Sith account{% endtrans %}"/>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class Forum(models.Model):
|
|||||||
Forum._club_memberships[self.id] = {}
|
Forum._club_memberships[self.id] = {}
|
||||||
Forum._club_memberships[self.id][user.id] = m
|
Forum._club_memberships[self.id][user.id] = m
|
||||||
if m:
|
if m:
|
||||||
return m.role > settings.SITH_MAXIMUM_FREE_ROLE
|
return m.role.is_board
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_loop(self):
|
def check_loop(self):
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ from django.conf import settings
|
|||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from model_bakery import baker
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, ClubRole, Membership
|
||||||
from core.models import Group, Page, SithFile, User
|
from core.models import Group, Page, SithFile, User
|
||||||
from core.utils import RED_PIXEL_PNG
|
from core.utils import RED_PIXEL_PNG
|
||||||
from sas.models import Album, PeoplePictureRelation, Picture
|
from sas.models import Album, PeoplePictureRelation, Picture
|
||||||
@@ -217,11 +218,19 @@ class Command(BaseCommand):
|
|||||||
"The `make_clubs()` method must be called before `make_club_memberships()`"
|
"The `make_clubs()` method must be called before `make_club_memberships()`"
|
||||||
)
|
)
|
||||||
memberships = []
|
memberships = []
|
||||||
|
roles = {
|
||||||
|
r.club_id: r.id
|
||||||
|
for r in baker.make(
|
||||||
|
ClubRole,
|
||||||
|
club=iter(self.clubs),
|
||||||
|
_quantity=len(self.clubs),
|
||||||
|
_bulk_create=True,
|
||||||
|
)
|
||||||
|
}
|
||||||
for i in range(1, 11): # users can be in up to 20 clubs
|
for i in range(1, 11): # users can be in up to 20 clubs
|
||||||
self.logger.info(f"Club membership, pass {i}")
|
self.logger.info(f"Club membership, pass {i}")
|
||||||
for uid in range(
|
for uid in range(i, self.NB_USERS, i):
|
||||||
i, self.NB_USERS, i
|
# Pass #1 will make sure every user is at least in one club
|
||||||
): # Pass #1 will make sure every user is at least in one club
|
|
||||||
user = self.users[uid]
|
user = self.users[uid]
|
||||||
club = self.clubs[(uid + i**2) % self.NB_CLUBS]
|
club = self.clubs[(uid + i**2) % self.NB_CLUBS]
|
||||||
|
|
||||||
@@ -236,7 +245,7 @@ class Command(BaseCommand):
|
|||||||
Membership(
|
Membership(
|
||||||
user=user,
|
user=user,
|
||||||
club=club,
|
club=club,
|
||||||
role=(uid + i) % 10 + 1, # spread the different roles
|
role_id=roles[club.id],
|
||||||
start_date=start,
|
start_date=start,
|
||||||
end_date=end,
|
end_date=end,
|
||||||
)
|
)
|
||||||
@@ -259,7 +268,7 @@ class Command(BaseCommand):
|
|||||||
Membership(
|
Membership(
|
||||||
user=user,
|
user=user,
|
||||||
club=club,
|
club=club,
|
||||||
role=((uid // 10) + i) % 10 + 1, # spread the different roles
|
role_id=roles[club.id],
|
||||||
start_date=start,
|
start_date=start,
|
||||||
end_date=end,
|
end_date=end,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4214,18 +4214,6 @@ msgstr ""
|
|||||||
msgid "You can't buy a refilling with sith money"
|
msgid "You can't buy a refilling with sith money"
|
||||||
msgstr "Vous ne pouvez pas acheter un rechargement avec de l'argent du sith"
|
msgstr "Vous ne pouvez pas acheter un rechargement avec de l'argent du sith"
|
||||||
|
|
||||||
#: eboutic/views.py
|
|
||||||
msgid "I have read and accept"
|
|
||||||
msgstr "J'ai lu et j'accepte"
|
|
||||||
|
|
||||||
#: eboutic/views.py
|
|
||||||
msgid "the general terms and conditions"
|
|
||||||
msgstr "les conditions générales de vente"
|
|
||||||
|
|
||||||
#: eboutic/views.py
|
|
||||||
msgid "of the student assosiation of UTBM"
|
|
||||||
msgstr "de l'Association des étudiants de l'UTBM"
|
|
||||||
|
|
||||||
#: election/forms.py
|
#: election/forms.py
|
||||||
msgid "You have selected too many candidates."
|
msgid "You have selected too many candidates."
|
||||||
msgstr "Vous avez sélectionné trop de candidats."
|
msgstr "Vous avez sélectionné trop de candidats."
|
||||||
|
|||||||
@@ -355,7 +355,6 @@ SITH_TWITTER = "@ae_utbm"
|
|||||||
# AE configuration
|
# AE configuration
|
||||||
SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1)
|
SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1)
|
||||||
SITH_PDF_CLUB_ID = env.int("SITH_PDF_CLUB_ID", default=2)
|
SITH_PDF_CLUB_ID = env.int("SITH_PDF_CLUB_ID", default=2)
|
||||||
SITH_LAUNDERETTE_CLUB_ID = env.int("SITH_LAUNDERETTE_CLUB_ID", default=84)
|
|
||||||
|
|
||||||
# Main root for club pages
|
# Main root for club pages
|
||||||
SITH_CLUB_ROOT_PAGE = "clubs"
|
SITH_CLUB_ROOT_PAGE = "clubs"
|
||||||
@@ -483,13 +482,6 @@ SITH_LOG_OPERATION_TYPE = [
|
|||||||
|
|
||||||
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
|
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
|
||||||
|
|
||||||
SITH_ECOCUP_CONS = env.int("SITH_ECOCUP_CONS", default=1151)
|
|
||||||
|
|
||||||
SITH_ECOCUP_DECO = env.int("SITH_ECOCUP_DECO", default=1152)
|
|
||||||
|
|
||||||
# The limit is the maximum difference between cons and deco possible for a customer
|
|
||||||
SITH_ECOCUP_LIMIT = 3
|
|
||||||
|
|
||||||
# Defines pagination for cash summary
|
# Defines pagination for cash summary
|
||||||
SITH_COUNTER_CASH_SUMMARY_LENGTH = 50
|
SITH_COUNTER_CASH_SUMMARY_LENGTH = 50
|
||||||
|
|
||||||
@@ -512,7 +504,6 @@ SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = env.int(
|
|||||||
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = env.int(
|
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = env.int(
|
||||||
"SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS", default=2
|
"SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS", default=2
|
||||||
)
|
)
|
||||||
SITH_PRODUCTTYPE_SUBSCRIPTION = env.int("SITH_PRODUCTTYPE_SUBSCRIPTION", default=2)
|
|
||||||
|
|
||||||
# Number of weeks before the end of a subscription when the subscriber can resubscribe
|
# Number of weeks before the end of a subscription when the subscriber can resubscribe
|
||||||
SITH_SUBSCRIPTION_END = 10
|
SITH_SUBSCRIPTION_END = 10
|
||||||
@@ -583,35 +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
|
|
||||||
# In this case, SITH_MAXIMUM_FREE_ROLE=1 means that a user can
|
|
||||||
# set himself as "Membre actif" or "Curieux", but not higher
|
|
||||||
SITH_MAXIMUM_FREE_ROLE = 1
|
|
||||||
|
|
||||||
# Minutes to timeout the logged barmen
|
# Minutes to timeout the logged barmen
|
||||||
SITH_BARMAN_TIMEOUT = 30
|
SITH_BARMAN_TIMEOUT = 30
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.urls import reverse
|
|||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from pytest_django.asserts import assertRedirects
|
from pytest_django.asserts import assertRedirects
|
||||||
|
|
||||||
from club.models import Club, Membership
|
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 User
|
from core.models import User
|
||||||
|
|
||||||
@@ -15,7 +15,8 @@ class TestSubscriptionPermission(TestCase):
|
|||||||
cls.user: User = subscriber_user.make()
|
cls.user: User = subscriber_user.make()
|
||||||
cls.admin = baker.make(User, is_superuser=True)
|
cls.admin = baker.make(User, is_superuser=True)
|
||||||
cls.club = baker.make(Club)
|
cls.club = baker.make(Club)
|
||||||
baker.make(Membership, user=cls.user, club=cls.club, role=7)
|
role = baker.make(ClubRole, club=cls.club, is_board=True)
|
||||||
|
baker.make(Membership, user=cls.user, club=cls.club, role=role)
|
||||||
|
|
||||||
def test_give_permission(self):
|
def test_give_permission(self):
|
||||||
self.client.force_login(self.admin)
|
self.client.force_login(self.admin)
|
||||||
|
|||||||
@@ -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