From 292a443a09e0a59fc3aef62ffac9207aeffdee9d Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Fri, 24 Apr 2026 16:05:29 -0500 Subject: [PATCH] feat(epg): restrict program access based on user permissions and enhance channel filtering Co-authored-by: Copilot --- apps/epg/api_views.py | 11 ++++++++++- apps/epg/serializers.py | 19 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/apps/epg/api_views.py b/apps/epg/api_views.py index 5db8ec39..1a43b938 100644 --- a/apps/epg/api_views.py +++ b/apps/epg/api_views.py @@ -320,6 +320,15 @@ class ProgramViewSet(viewsets.ModelViewSet): queryset = queryset.filter(filters).distinct().order_by('start_time') + # Restrict results to programs on channels the user can access + user = request.user + if user.user_level < 10: + access_filter = Q(epg__channels__user_level__lte=user.user_level) + custom_props = user.custom_properties or {} + if custom_props.get('hide_adult_content', False): + access_filter &= Q(epg__channels__is_adult=False) + queryset = queryset.filter(access_filter).distinct() + # Resolve field selection before serialization so expensive methods can short-circuit requested_fields = params.get('fields') allowed = set(f.strip() for f in requested_fields.split(',')) if requested_fields else None @@ -327,7 +336,7 @@ class ProgramViewSet(viewsets.ModelViewSet): # Paginate paginator = ProgramSearchPagination() page = paginator.paginate_queryset(queryset, request) - serializer = ProgramSearchResultSerializer(page, many=True, context={'fields': allowed}) + serializer = ProgramSearchResultSerializer(page, many=True, context={'fields': allowed, 'user': request.user}) data = serializer.data if allowed: diff --git a/apps/epg/serializers.py b/apps/epg/serializers.py index 9f7e3c20..4ab89a9b 100644 --- a/apps/epg/serializers.py +++ b/apps/epg/serializers.py @@ -208,21 +208,32 @@ class ProgramSearchResultSerializer(serializers.ModelSerializer): 'channels', 'streams', ] + def _accessible_channels(self, obj): + """Return prefetched channels filtered to those the requesting user can access.""" + channels = list(obj.epg.channels.all()) if obj.epg else [] + user = self.context.get('user') + if user is None or user.user_level >= 10: + return channels + custom_props = user.custom_properties or {} + hide_adult = custom_props.get('hide_adult_content', False) + return [ + ch for ch in channels + if ch.user_level <= user.user_level and (not hide_adult or not ch.is_adult) + ] + def get_channels(self, obj): fields = self.context.get('fields') if fields is not None and 'channels' not in fields: return [] - channels = obj.epg.channels.all() if obj.epg else [] - return ProgramSearchChannelSerializer(channels, many=True).data + return ProgramSearchChannelSerializer(self._accessible_channels(obj), many=True).data def get_streams(self, obj): fields = self.context.get('fields') if fields is not None and 'streams' not in fields: return [] - channels = obj.epg.channels.all() if obj.epg else [] stream_ids = set() streams = [] - for ch in channels: + for ch in self._accessible_channels(obj): for s in ch.streams.all(): if s.id not in stream_ids: stream_ids.add(s.id)