diff --git a/src/api/serializers.py b/src/api/serializers.py index 2ac02e23e81d31d04572c295787c421cc36da605..087a3fc22b3d6710493193198cc3394d31874d75 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 9c36dd5e222b261983aa23cfd98d2ebc9e67a14f..8cc93edfcafef1aae7d8a3dc5578cfd40ae7f8a9 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 5d0e94b96cd7318fdcef0ac29f327012228ae2cd..bae1782e3d2ec3300503a5cc088ed2bc034c9499 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 4b29353b1cc69dd24ce697552438484638f2754f..2be313cf94632415f30a5adc0076864515b1af20 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 7e8139efa490eb36edf4637b6892b26b421b71d9..79921d45d3d1849922983b439ade04cb7b59f678 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 345845c0f7211c73f32be24b07973ceede59e269..07e949411f6f6c18ecc71e7ee565237d638b058e 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 b53bcc16c7650d432a0208d1fe376f31e560fa7f..7afc9a73b718685c7a61aa195b78841ae0e838c6 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]})