feat: make poster views available to club board members

This commit is contained in:
imperosol
2025-09-19 16:21:15 +02:00
parent c6baab068a
commit 08b16d6e74
10 changed files with 242 additions and 219 deletions

View File

@@ -42,6 +42,13 @@ from core.fields import ResizedImageField
from core.models import Group, Notification, Page, SithFile, User
class ClubQuerySet(models.QuerySet):
def having_board_member(self, user: User) -> Self:
"""Filter all club in which the given user is a board member."""
active_memberships = user.memberships.board().ongoing()
return self.filter(Exists(active_memberships.filter(club=OuterRef("pk"))))
class Club(models.Model):
"""The Club class, made as a tree to allow nice tidy organization."""
@@ -91,6 +98,8 @@ class Club(models.Model):
Group, related_name="club_board", on_delete=models.PROTECT
)
objects = ClubQuerySet.as_manager()
class Meta:
ordering = ["name"]

27
club/tests/test_club.py Normal file
View File

@@ -0,0 +1,27 @@
from datetime import timedelta
import pytest
from django.utils.timezone import localdate
from model_bakery import baker
from model_bakery.recipe import Recipe
from club.models import Club, Membership
from core.baker_recipes import subscriber_user
@pytest.mark.django_db
def test_club_queryset_having_board_member():
clubs = baker.make(Club, _quantity=5)
user = subscriber_user.make()
membership_recipe = Recipe(
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(
club=clubs[3], role=3, end_date=localdate() - timedelta(days=1)
)
club_ids = Club.objects.having_board_member(user).values_list("id", flat=True)
assert set(club_ids) == {clubs[1].id, clubs[2].id}

View File

@@ -0,0 +1,35 @@
import pytest
from django.test import Client
from django.urls import reverse
from model_bakery import baker
from club.models import Club
from com.models import Poster
from core.baker_recipes import subscriber_user
@pytest.mark.django_db
@pytest.mark.parametrize("route_url", ["club:poster_list", "club:poster_create"])
def test_access(client: Client, route_url):
club = baker.make(Club)
user = subscriber_user.make()
url = reverse(route_url, kwargs={"club_id": club.id})
client.force_login(user)
assert client.get(url).status_code == 403
club.board_group.users.add(user)
assert client.get(url).status_code == 200
@pytest.mark.django_db
@pytest.mark.parametrize("route_url", ["club:poster_edit", "club:poster_delete"])
def test_access_specific_poster(client: Client, route_url):
club = baker.make(Club)
user = subscriber_user.make()
poster = baker.make(Poster)
url = reverse(route_url, kwargs={"club_id": club.id, "poster_id": poster.id})
client.force_login(user)
assert client.get(url).status_code == 403
club.board_group.users.add(user)
assert client.get(url).status_code == 200

View File

@@ -51,13 +51,17 @@ from club.forms import (
SellingsForm,
)
from club.models import Club, Mailing, MailingSubscription, Membership
from com.models import Poster
from com.views import (
PosterCreateBaseView,
PosterDeleteBaseView,
PosterEditBaseView,
PosterListBaseView,
)
from core.auth.mixins import CanCreateMixin, CanEditMixin, CanViewMixin
from core.auth.mixins import (
CanEditMixin,
CanViewMixin,
)
from core.models import PageRev
from core.views import DetailFormView, PageEditViewBase
from core.views.mixins import TabedViewMixin
@@ -66,9 +70,12 @@ from counter.models import Selling
class ClubTabsMixin(TabedViewMixin):
def get_tabs_title(self):
obj = self.get_object()
if isinstance(obj, PageRev):
self.object = obj.page.club
if not hasattr(self, "object") or not self.object:
self.object = self.get_object()
if isinstance(self.object, PageRev):
self.object = self.object.page.club
elif isinstance(self.object, Poster):
self.object = self.object.club
return self.object.get_display_name()
def get_list_of_tabs(self):
@@ -159,7 +166,7 @@ class ClubTabsMixin(TabedViewMixin):
"club:poster_list", kwargs={"club_id": self.object.id}
),
"slug": "posters",
"name": _("Posters list"),
"name": _("Posters"),
},
]
)
@@ -686,48 +693,45 @@ class MailingAutoGenerationView(View):
return redirect("club:mailing", club_id=club.id)
class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin):
class PosterListView(ClubTabsMixin, PosterListBaseView):
"""List communication posters."""
current_tab = "posters"
extra_context = {"app": "club"}
def get_queryset(self):
return super().get_queryset().filter(club=self.club.id)
def get_object(self):
return self.club
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "club"
kwargs["club"] = self.club
return kwargs
class PosterCreateView(PosterCreateBaseView, CanCreateMixin):
class PosterCreateView(ClubTabsMixin, PosterCreateBaseView):
"""Create communication poster."""
pk_url_kwarg = "club_id"
def get_object(self):
obj = super().get_object()
if not obj:
return self.club
return obj
current_tab = "posters"
def get_success_url(self, **kwargs):
return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id})
def get_object(self, *args, **kwargs):
return self.club
class PosterEditView(ClubTabsMixin, PosterEditBaseView, CanEditMixin):
class PosterEditView(ClubTabsMixin, PosterEditBaseView):
"""Edit communication poster."""
current_tab = "posters"
extra_context = {"app": "club"}
def get_success_url(self):
return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id})
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "club"
return kwargs
class PosterDeleteView(PosterDeleteBaseView, ClubTabsMixin, CanEditMixin):
class PosterDeleteView(ClubTabsMixin, PosterDeleteBaseView):
"""Delete communication poster."""
current_tab = "posters"
def get_success_url(self):
return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id})