mirror of
https://github.com/ae-utbm/sith.git
synced 2025-10-09 16:24:39 +00:00
feat: MembershipQuerySet.editable_by
method
This commit is contained in:
@@ -30,7 +30,8 @@ from django.core.cache import cache
|
|||||||
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
|
from django.db.models import Exists, F, OuterRef, Q, Value
|
||||||
|
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
|
||||||
@@ -252,6 +253,41 @@ class MembershipQuerySet(models.QuerySet):
|
|||||||
"""
|
"""
|
||||||
return self.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE)
|
return self.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE)
|
||||||
|
|
||||||
|
def editable_by(self, user: User) -> Self:
|
||||||
|
"""Filter Memberships that this user can edit.
|
||||||
|
|
||||||
|
Users with the `club.change_membership` permission can edit all Membership.
|
||||||
|
The other users can end :
|
||||||
|
- their own membership
|
||||||
|
- if they are board members, memberships with a role lower than their own
|
||||||
|
|
||||||
|
For example, let's suppose the following users :
|
||||||
|
- A : board member
|
||||||
|
- B : board member
|
||||||
|
- C : simple member
|
||||||
|
- D : curious
|
||||||
|
|
||||||
|
A will be able to end the memberships of A, C and D ;
|
||||||
|
C and D will be able to end only their own membership.
|
||||||
|
"""
|
||||||
|
if user.has_perm("club.change_membership"):
|
||||||
|
return self.all()
|
||||||
|
return self.filter(
|
||||||
|
Exists(
|
||||||
|
Membership.objects.filter(
|
||||||
|
Q(
|
||||||
|
role__gt=Greatest(
|
||||||
|
OuterRef("role"), Value(settings.SITH_MAXIMUM_FREE_ROLE)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
| Q(pk=OuterRef("pk")),
|
||||||
|
user=user,
|
||||||
|
end_date=None,
|
||||||
|
club=OuterRef("club"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def update(self, **kwargs) -> int:
|
def update(self, **kwargs) -> int:
|
||||||
"""Refresh the cache and edit group ownership.
|
"""Refresh the cache and edit group ownership.
|
||||||
|
|
||||||
@@ -328,16 +364,12 @@ class Membership(models.Model):
|
|||||||
User,
|
User,
|
||||||
verbose_name=_("user"),
|
verbose_name=_("user"),
|
||||||
related_name="memberships",
|
related_name="memberships",
|
||||||
null=False,
|
|
||||||
blank=False,
|
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
club = models.ForeignKey(
|
club = models.ForeignKey(
|
||||||
Club,
|
Club,
|
||||||
verbose_name=_("club"),
|
verbose_name=_("club"),
|
||||||
related_name="members",
|
related_name="members",
|
||||||
null=False,
|
|
||||||
blank=False,
|
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
start_date = models.DateField(_("start date"), default=timezone.now)
|
start_date = models.DateField(_("start date"), default=timezone.now)
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
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
|
||||||
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import localdate, localtime, now
|
from django.utils.timezone import localdate, localtime, now
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
from club.forms import ClubMemberForm
|
from club.forms import ClubMemberForm
|
||||||
from club.models import Membership
|
from club.models import Club, 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
|
||||||
@@ -137,6 +139,38 @@ class TestMembershipQuerySet(TestClub):
|
|||||||
assert set(user.groups.all()).isdisjoint(club_groups)
|
assert set(user.groups.all()).isdisjoint(club_groups)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMembershipEditableBy(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
Membership.objects.all().delete()
|
||||||
|
cls.club_a, cls.club_b = baker.make(Club, _quantity=2)
|
||||||
|
cls.memberships = [
|
||||||
|
*baker.make(
|
||||||
|
Membership, role=iter([7, 3, 3, 1]), club=cls.club_a, _quantity=4
|
||||||
|
),
|
||||||
|
*baker.make(
|
||||||
|
Membership, role=iter([7, 3, 3, 1]), club=cls.club_b, _quantity=4
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_admin_user(self):
|
||||||
|
perm = Permission.objects.get(codename="change_membership")
|
||||||
|
user = baker.make(User, user_permissions=[perm])
|
||||||
|
qs = Membership.objects.editable_by(user).values_list("id", flat=True)
|
||||||
|
assert set(qs) == set(Membership.objects.values_list("id", flat=True))
|
||||||
|
|
||||||
|
def test_simple_subscriber_user(self):
|
||||||
|
user = subscriber_user.make()
|
||||||
|
assert not Membership.objects.editable_by(user).exists()
|
||||||
|
|
||||||
|
def test_board_member(self):
|
||||||
|
# a board member can end lower memberships and its own one
|
||||||
|
user = self.memberships[2].user
|
||||||
|
qs = Membership.objects.editable_by(user).values_list("id", flat=True)
|
||||||
|
expected = {self.memberships[2].id, self.memberships[3].id}
|
||||||
|
assert set(qs) == expected
|
||||||
|
|
||||||
|
|
||||||
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: int):
|
||||||
"""Assert that the given membership is active and started today."""
|
"""Assert that the given membership is active and started today."""
|
||||||
|
Reference in New Issue
Block a user