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

@@ -2,7 +2,6 @@ from datetime import date
from dateutil.relativedelta import relativedelta
from django import forms
from django.db.models import Exists, OuterRef
from django.forms import CheckboxInput
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -35,20 +34,18 @@ class PosterForm(forms.ModelForm):
label=_("Start date"),
widget=SelectDateTime,
required=True,
initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
initial=timezone.now(),
)
date_end = forms.DateTimeField(
label=_("End date"), widget=SelectDateTime, required=False
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
def __init__(self, *args, user: User, **kwargs):
super().__init__(*args, **kwargs)
if self.user and not self.user.is_com_admin:
self.fields["club"].queryset = Club.objects.filter(
id__in=self.user.clubs_with_rights
)
self.fields.pop("display_time")
if user.is_root or user.is_com_admin:
self.fields["club"].widget = AutoCompleteSelectClub()
else:
self.fields["club"].queryset = Club.objects.having_board_member(user)
class NewsDateForm(forms.ModelForm):
@@ -161,16 +158,9 @@ class NewsForm(forms.ModelForm):
# if the author is an admin, he/she can choose any club,
# otherwise, only clubs for which he/she is a board member can be selected
if author.is_root or author.is_com_admin:
self.fields["club"] = forms.ModelChoiceField(
queryset=Club.objects.all(), widget=AutoCompleteSelectClub
)
self.fields["club"].widget = AutoCompleteSelectClub()
else:
active_memberships = author.memberships.board().ongoing()
self.fields["club"] = forms.ModelChoiceField(
queryset=Club.objects.filter(
Exists(active_memberships.filter(club=OuterRef("pk")))
)
)
self.fields["club"].queryset = Club.objects.having_board_member(author)
def is_valid(self):
return super().is_valid() and self.date_form.is_valid()

View File

@@ -412,17 +412,5 @@ class Poster(models.Model):
if self.date_end and self.date_begin > self.date_end:
raise ValidationError(_("Begin date should be before end date"))
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_com_admin or len(user.clubs_with_rights) > 0
def can_be_moderated_by(self, user):
return user.is_com_admin
def get_display_name(self):
return self.club.get_display_name()
@property
def page(self):
return self.club.page

View File

@@ -18,17 +18,16 @@ from unittest.mock import patch
import pytest
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client, TestCase
from django.urls import reverse
from django.utils import html
from django.utils.timezone import localtime, now
from django.utils.timezone import now
from django.utils.translation import gettext as _
from model_bakery import baker
from pytest_django.asserts import assertNumQueries, assertRedirects
from club.models import Club, Membership
from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle
from com.models import News, NewsDate, Sith, Weekmail, WeekmailArticle
from core.baker_recipes import subscriber_user
from core.models import AnonymousUser, Group, User
@@ -207,31 +206,6 @@ class TestWeekmailArticle(TestCase):
assert not self.article.is_owned_by(self.sli)
class TestPoster(TestCase):
@classmethod
def setUpTestData(cls):
cls.com_admin = User.objects.get(username="comunity")
cls.poster = Poster.objects.create(
name="dummy",
file=SimpleUploadedFile("dummy.jpg", b"azertyuiop"),
club=Club.objects.first(),
date_begin=localtime(now()),
)
cls.sli = User.objects.get(username="sli")
cls.sli.memberships.all().delete()
Membership(user=cls.sli, club=Club.objects.first(), role=5).save()
cls.susbcriber = User.objects.get(username="subscriber")
cls.anonymous = AnonymousUser()
def test_poster_owner(self):
"""Test that poster are owned by com admins and board members in clubs."""
assert self.poster.is_owned_by(self.com_admin)
assert not self.poster.is_owned_by(self.anonymous)
assert not self.poster.is_owned_by(self.susbcriber)
assert self.poster.is_owned_by(self.sli)
class TestNewsCreation(TestCase):
@classmethod
def setUpTestData(cls):

View File

@@ -28,7 +28,9 @@ from typing import Any
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin,
)
from django.contrib.syndication.views import Feed
from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Max
@@ -50,6 +52,7 @@ from core.auth.mixins import (
CanEditPropMixin,
CanViewMixin,
PermissionOrAuthorRequiredMixin,
PermissionOrClubBoardRequiredMixin,
)
from core.models import User
from core.views.mixins import QuickNotifMixin, TabedViewMixin
@@ -99,13 +102,6 @@ class ComTabsMixin(TabedViewMixin):
]
class IsComAdminMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_com_admin:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView):
model = Sith
template_name = "core/edit.jinja"
@@ -558,161 +554,109 @@ class MailingModerateView(View):
raise PermissionDenied
class PosterAdminViewMixin(IsComAdminMixin, ComTabsMixin):
current_tab = "posters"
class PosterListBaseView(PosterAdminViewMixin, ListView):
class PosterListBaseView(PermissionOrClubBoardRequiredMixin, ListView):
"""List communication posters."""
current_tab = "posters"
model = Poster
template_name = "com/poster_list.jinja"
def dispatch(self, request, *args, **kwargs):
club_id = kwargs.pop("club_id", None)
self.club = None
if club_id:
self.club = get_object_or_404(Club, pk=club_id)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
if self.request.user.is_com_admin:
return Poster.objects.all().order_by("-date_begin")
else:
return Poster.objects.filter(club=self.club.id)
permission_required = "com.view_poster"
ordering = ["-date_begin"]
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
if not self.request.user.is_com_admin:
kwargs["club"] = self.club
return kwargs
return super().get_context_data(**kwargs) | {"club": self.club}
class PosterCreateBaseView(PosterAdminViewMixin, CreateView):
class PosterCreateBaseView(PermissionOrClubBoardRequiredMixin, CreateView):
"""Create communication poster."""
current_tab = "posters"
form_class = PosterForm
template_name = "core/create.jinja"
permission_required = "com.add_poster"
def get_queryset(self):
return Poster.objects.all()
def dispatch(self, request, *args, **kwargs):
if "club_id" in kwargs:
self.club = get_object_or_404(Club, pk=kwargs["club_id"])
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
return super().get_form_kwargs() | {"user": self.request.user}
def get_initial(self):
return {"club": self.club}
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
if not self.request.user.is_com_admin:
kwargs["club"] = self.club
return kwargs
return super().get_context_data(**kwargs) | {"club": self.club}
def form_valid(self, form):
if self.request.user.is_com_admin:
if self.request.user.has_perm("com.moderate_poster"):
form.instance.is_moderated = True
return super().form_valid(form)
class PosterEditBaseView(PosterAdminViewMixin, UpdateView):
class PosterEditBaseView(PermissionOrClubBoardRequiredMixin, UpdateView):
"""Edit communication poster."""
pk_url_kwarg = "poster_id"
current_tab = "posters"
form_class = PosterForm
template_name = "com/poster_edit.jinja"
def get_initial(self):
return {
"date_begin": self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S")
if self.object.date_begin
else None,
"date_end": self.object.date_end.strftime("%Y-%m-%d %H:%M:%S")
if self.object.date_end
else None,
}
def dispatch(self, request, *args, **kwargs):
if kwargs.get("club_id"):
try:
self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist as e:
raise PermissionDenied from e
return super().dispatch(request, *args, **kwargs)
permission_required = "com.change_poster"
def get_queryset(self):
return Poster.objects.all()
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({"user": self.request.user})
return kwargs
return super().get_form_kwargs() | {"user": self.request.user}
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
if hasattr(self, "club"):
kwargs["club"] = self.club
return kwargs
return super().get_context_data(**kwargs) | {"club": self.club}
def form_valid(self, form):
if self.request.user.is_com_admin:
if not self.request.user.has_perm("com.moderate_poster"):
form.instance.is_moderated = False
return super().form_valid(form)
class PosterDeleteBaseView(PosterAdminViewMixin, DeleteView):
class PosterDeleteBaseView(
PermissionOrClubBoardRequiredMixin, ComTabsMixin, DeleteView
):
"""Edit communication poster."""
pk_url_kwarg = "poster_id"
current_tab = "posters"
model = Poster
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
if kwargs.get("club_id"):
try:
self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist as e:
raise PermissionDenied from e
return super().dispatch(request, *args, **kwargs)
permission_required = "com.delete_poster"
class PosterListView(PosterListBaseView):
class PosterListView(ComTabsMixin, PosterListBaseView):
"""List communication posters."""
current_tab = "posters"
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.has_perm("com.view_poster"):
return qs
return qs.filter(club=self.club.id)
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "com"
return kwargs
class PosterCreateView(PosterCreateBaseView):
class PosterCreateView(ComTabsMixin, PosterCreateBaseView):
"""Create communication poster."""
current_tab = "posters"
success_url = reverse_lazy("com:poster_list")
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "com"
return kwargs
extra_context = {"app": "com"}
class PosterEditView(PosterEditBaseView):
class PosterEditView(ComTabsMixin, PosterEditBaseView):
"""Edit communication poster."""
current_tab = "posters"
success_url = reverse_lazy("com:poster_list")
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "com"
return kwargs
extra_context = {"app": "com"}
class PosterDeleteView(PosterDeleteBaseView):
@@ -721,44 +665,39 @@ class PosterDeleteView(PosterDeleteBaseView):
success_url = reverse_lazy("com:poster_list")
class PosterModerateListView(PosterAdminViewMixin, ListView):
class PosterModerateListView(PermissionRequiredMixin, ComTabsMixin, ListView):
"""Moderate list communication poster."""
current_tab = "posters"
model = Poster
template_name = "com/poster_moderate.jinja"
queryset = Poster.objects.filter(is_moderated=False).all()
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "com"
return kwargs
permission_required = "com.moderate_poster"
extra_context = {"app": "com"}
class PosterModerateView(PosterAdminViewMixin, View):
class PosterModerateView(PermissionRequiredMixin, ComTabsMixin, View):
"""Moderate communication poster."""
current_tab = "posters"
permission_required = "com.moderate_poster"
extra_context = {"app": "com"}
def get(self, request, *args, **kwargs):
obj = get_object_or_404(Poster, pk=kwargs["object_id"])
if obj.can_be_moderated_by(request.user):
obj.is_moderated = True
obj.moderator = request.user
obj.save()
return redirect("com:poster_moderate_list")
raise PermissionDenied
def get_context_data(self, **kwargs):
kwargs = super(PosterModerateListView, self).get_context_data(**kwargs)
kwargs["app"] = "com"
return kwargs
obj.is_moderated = True
obj.moderator = request.user
obj.save()
return redirect("com:poster_moderate_list")
class ScreenListView(IsComAdminMixin, ComTabsMixin, ListView):
class ScreenListView(PermissionRequiredMixin, ComTabsMixin, ListView):
"""List communication screens."""
current_tab = "screens"
model = Screen
template_name = "com/screen_list.jinja"
permission_required = "com.view_screen"
class ScreenSlideshowView(DetailView):
@@ -769,12 +708,12 @@ class ScreenSlideshowView(DetailView):
template_name = "com/screen_slideshow.jinja"
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["posters"] = self.object.active_posters()
return kwargs
return super().get_context_data(**kwargs) | {
"posters": self.object.active_posters()
}
class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView):
class ScreenCreateView(PermissionRequiredMixin, ComTabsMixin, CreateView):
"""Create communication screen."""
current_tab = "screens"
@@ -782,9 +721,10 @@ class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView):
fields = ["name"]
template_name = "core/create.jinja"
success_url = reverse_lazy("com:screen_list")
permission_required = "com.add_screen"
class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView):
class ScreenEditView(PermissionRequiredMixin, ComTabsMixin, UpdateView):
"""Edit communication screen."""
pk_url_kwarg = "screen_id"
@@ -793,9 +733,10 @@ class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView):
fields = ["name"]
template_name = "com/screen_edit.jinja"
success_url = reverse_lazy("com:screen_list")
permission_required = "com.change_screen"
class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView):
class ScreenDeleteView(PermissionRequiredMixin, ComTabsMixin, DeleteView):
"""Delete communication screen."""
pk_url_kwarg = "screen_id"
@@ -803,3 +744,4 @@ class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView):
model = Screen
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("com:screen_list")
permission_required = "com.delete_screen"