From 57c1cc8e5798cc2d57cd39aaa053ea6110bd701e Mon Sep 17 00:00:00 2001 From: Lucas Brandstaetter <lucas@brandstaetter.tech> Date: Thu, 21 Dec 2023 13:38:57 +0100 Subject: [PATCH] Update AssemblyManager to ConferenceManagerMixin --- src/api/serializers.py | 2 +- src/api/views/mixins.py | 2 +- src/backoffice/views/assemblies.py | 8 +-- src/backoffice/views/assemblyteam.py | 3 +- src/core/models/assemblies.py | 86 +++++----------------------- src/core/models/badges.py | 2 +- src/core/models/base_managers.py | 4 +- 7 files changed, 26 insertions(+), 81 deletions(-) diff --git a/src/api/serializers.py b/src/api/serializers.py index 2ac02e23e..087a3fc22 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -157,7 +157,7 @@ class AssemblySerializer(HubModelSerializer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - cluster_qs = Assembly.objects.accessible_by_user(conference=self.conference, user=self.request_user).exclude(hierarchy=Assembly.Hierarchy.REGULAR) + cluster_qs = Assembly.objects.associated_with_user(conference=self.conference, user=self.request_user).exclude(hierarchy=Assembly.Hierarchy.REGULAR) self.fields['parent'].queryset = cluster_qs diff --git a/src/api/views/mixins.py b/src/api/views/mixins.py index 9c36dd5e2..8cc93edfc 100644 --- a/src/api/views/mixins.py +++ b/src/api/views/mixins.py @@ -56,7 +56,7 @@ class ConferenceSlugAssemblyMixin(ConferenceSlugMixin): if self._assembly is None: assembly_slug = self.request.resolver_match.kwargs['assembly'] try: - self._assembly = Assembly.objects.accessible_by_user(self.request.user, self.conference).get(slug=assembly_slug) + self._assembly = Assembly.objects.associated_with_user(self.conference, user=self.request.user).get(slug=assembly_slug) except Assembly.DoesNotExist: if issuing_token := self.kwargs.get('issuing_token', None): try: diff --git a/src/backoffice/views/assemblies.py b/src/backoffice/views/assemblies.py index 5d0e94b96..bae1782e3 100644 --- a/src/backoffice/views/assemblies.py +++ b/src/backoffice/views/assemblies.py @@ -448,7 +448,7 @@ class AssemblyEditChildrenView(AssemblyMixin, View): def get(self, *args, **kwargs): candidates_qs = ( - Assembly.objects.accessible_by_user(conference=self.conference, user=self.request.user) + Assembly.objects.associated_with_user(conference=self.conference, user=self.request.user) .filter(hierarchy=Assembly.Hierarchy.REGULAR, parent=None) .exclude(state_assembly__in=[Assembly.State.NONE, Assembly.State.REJECTED, Assembly.State.HIDDEN, Assembly.State.PLANNED]) ) @@ -481,7 +481,7 @@ class AssemblyEditLinksView(AssemblyMixin, View): if add_id is not None: try: - linkee = Assembly.objects.accessible_by_user(user=request.user, conference=self.conference).get(pk=add_id) + linkee = Assembly.objects.associated_with_user(user=request.user, conference=self.conference).get(pk=add_id) except Assembly.DoesNotExist: messages.warning('404 +Assembly %s', add_id) linkee = None @@ -502,7 +502,7 @@ class AssemblyEditLinksView(AssemblyMixin, View): if remove_id is not None: try: - linkee = Assembly.objects.accessible_by_user(user=request.user, conference=self.conference).get(pk=remove_id) + linkee = Assembly.objects.associated_with_user(user=request.user, conference=self.conference).get(pk=remove_id) except Assembly.DoesNotExist: messages.warning('404 -Assembly %s', remove_id) linkee = None @@ -524,7 +524,7 @@ class AssemblyEditLinksView(AssemblyMixin, View): def get(self, *args, **kwargs): candidates_qs = ( - Assembly.objects.accessible_by_user(conference=self.conference, user=self.request.user) + Assembly.objects.associated_with_user(conference=self.conference, user=self.request.user) .filter(hierarchy=Assembly.Hierarchy.REGULAR) .exclude(state_assembly__in=[Assembly.State.NONE, Assembly.State.PLANNED, Assembly.State.HIDDEN, Assembly.State.REJECTED]) ) diff --git a/src/backoffice/views/assemblyteam.py b/src/backoffice/views/assemblyteam.py index 4b29353b1..2be313cf9 100644 --- a/src/backoffice/views/assemblyteam.py +++ b/src/backoffice/views/assemblyteam.py @@ -122,8 +122,7 @@ class AssembliesListMixin(AssemblyTeamMixin): # get the mode mode = (self.request.POST if self.request.method == 'POST' else self.request.GET).get('mode', self.default_assemblies_mode) - # not using .accessible_by_user() here as we're the Assembly Team anyway - qs = Assembly.objects.filter(conference=self.conference) + qs = Assembly.objects.associated_with_user(conference=self.conference, user=self.request.user, staff_can_see=True) if mode == 'not_selected': pass elif self.active_page == 'assemblies': diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py index 7e8139efa..79921d45d 100644 --- a/src/core/models/assemblies.py +++ b/src/core/models/assemblies.py @@ -19,6 +19,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ +from core.models.base_managers import ConferenceManagerMixin from core.models.tags import TagItem from core.validators import FileSizeValidator, ImageDimensionValidator @@ -31,85 +32,28 @@ from .tags import TaggedItemMixin from .users import PlatformUser -class AssemblyManager(models.Manager): - def accessible_by_user(self, user: PlatformUser, conference: Conference): - assert user is not None +class AssemblyManager(ConferenceManagerMixin['Assembly']): + staff_permissions = ['assembly_team'] + assembly_filter = 'self' - qs = self.get_queryset() - if conference is not None: - qs = qs.filter(conference=conference) - - # crude hack because Django makes it that hard to customize AnonymousUser - if user is None or not user.is_authenticated: - user = PlatformUser.get_anonymous_user() - - # see if we can return all assemblies (i.e. the user is marked as "staff") - if user.is_authenticated: - try: - member = ConferenceMember.objects.get(conference=conference, user=user) - if member.is_staff: - return qs - except ConferenceMember.DoesNotExist: - pass - - # for everybody else, only show "public" assemblies - return qs.filter(state_assembly__in=self.model.PUBLIC_STATES) - - def associated_to_user(self, user: PlatformUser, conference: Conference): - assert user is not None + def apply_public_filter(self, queryset: 'QuerySet[Assembly]', member: ConferenceMember | None = None) -> 'QuerySet[Assembly]': + return queryset.filter(Q(state_assembly__in=self.model.PUBLIC_STATES) | Q(state_channel__in=self.model.PUBLIC_STATES)) + def associated_to_user(self, user: PlatformUser, conference: Conference) -> 'QuerySet[Assembly]': # guests cannot manage anything if user is None or not user.is_authenticated: return Assembly.objects.none() - - # prepare query - qs = self.get_queryset() - if conference is not None: - qs = qs.filter(conference=conference) - - # lookup via ConferenceMember - qs = qs.filter(members__member=user) - - # but don't show "hidden" ones to the user - qs = qs.exclude(state_assembly__in=[Assembly.State.NONE], state_channel__in=[Assembly.State.NONE]) - - # finally return the resulting QuerySet - return qs - - def conference_accessible(self, conference: Conference): return ( - self.get_queryset() - .filter(conference=conference) - .filter(Q(state_assembly__in=self.model.PUBLIC_STATES) | Q(state_channel__in=self.model.PUBLIC_STATES)) + self.associated_with_user(conference, user=user) + .filter(members__member=user) + .exclude( + Q( + state_assembly__in=[Assembly.State.NONE, Assembly.State.HIDDEN], + state_channel__in=[Assembly.State.NONE, Assembly.State.HIDDEN], + ) + ) ) - def manageable_by_user(self, user: PlatformUser, conference: Conference, staff_can_manage=True): - assert user is not None - - # guests cannot manage anything - if user is None or not user.is_authenticated: - return Assembly.objects.none() - - # prepare query - qs = self.get_queryset() - if conference is not None: - qs = qs.filter(conference=conference) - - # see if we can return all assemblies (i.e. the user is marked as "staff") - if staff_can_manage: - try: - member = ConferenceMember.objects.get(conference=conference, user=user) - if member.is_staff and member.has_perm('core.assembly_team'): - return qs - except ConferenceMember.DoesNotExist: - pass - - # lookup via AssemblyMember - qs = qs.filter(members__member=user, members__can_manage_assembly=True) - - # finally return the resulting QuerySet - return qs - def get_banner_file_name(instance: 'Assembly', filename: str): return str(Path(str(instance.id)).joinpath('banner', filename)) diff --git a/src/core/models/badges.py b/src/core/models/badges.py index 345845c0f..07e949411 100644 --- a/src/core/models/badges.py +++ b/src/core/models/badges.py @@ -77,7 +77,7 @@ class BadgeManager(ConferenceManagerMixin['Badge']): if not user.is_authenticated: return qs.filter(state=Badge.State.PUBLIC) - manageable = Assembly.objects.manageable_by_user(user, conference, staff_can_manage) + manageable = Assembly.objects.manageable_by_user(conference, user=user, staff_can_manage=staff_can_manage) return qs.filter(Q(state=Badge.State.PUBLIC) | Q(users__user=user) | Q(issuing_assembly__in=manageable)) diff --git a/src/core/models/base_managers.py b/src/core/models/base_managers.py index b53bcc16c..7afc9a73b 100644 --- a/src/core/models/base_managers.py +++ b/src/core/models/base_managers.py @@ -5,7 +5,6 @@ from django.core.exceptions import ImproperlyConfigured from django.db import models from django.db.models import Model, QuerySet -from core.models.assemblies import Assembly, AssemblyMember from core.models.conference import ConferenceMember if TYPE_CHECKING: @@ -75,6 +74,9 @@ class ConferenceManagerMixin(models.Manager, Generic[_ModelType]): else: prefix = f'{self.assembly_filter}__' queryset = queryset.select_related(self.assembly_filter) + + from core.models.assemblies import Assembly, AssemblyMember # pylint: disable=C0415 # Circular import avoidance + allowed_members = AssemblyMember.objects.filter(member=member.user) allowed_members = allowed_members.filter(can_manage_assembly=True) if only_manageable else allowed_members queryset = queryset.filter(**{f'{prefix}members__in': allowed_members}).exclude(**{f'{prefix}state_assembly__in': [Assembly.State.HIDDEN]}) -- GitLab