diff --git a/quackscape/tours/models.py b/quackscape/tours/models.py index 8ceb5b5..21c6dc2 100644 --- a/quackscape/tours/models.py +++ b/quackscape/tours/models.py @@ -47,6 +47,7 @@ class Category(models.Model): def __str__(self): return f"{self.title} ({self.owner})" + class CategoryPermission(models.Model): category = models.ForeignKey(Category, on_delete=models.CASCADE) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) @@ -191,7 +192,9 @@ class OriginalMedia(PolymorphicModel): file = models.FileField(upload_to=upload_to) width = models.IntegerField(blank=True, null=True) height = models.IntegerField(blank=True, null=True) - category = models.ForeignKey(Category, related_name="media", on_delete=models.SET_NULL, null=True) + category = models.ForeignKey( + Category, related_name="media", on_delete=models.SET_NULL, null=True + ) def __str__(self): return self.title @@ -204,6 +207,18 @@ class OriginalMedia(PolymorphicModel): def thumbnail(self) -> str: return self.resolutions.all().order_by("width").first() + def user_has_permission(self, user): + return user.is_authenticated and ( + user.is_superuser + or user.is_staff + or (self.category and self.category.user_has_permission(user)) + ) + + def user_has_view_permission(self, user): + return self.user_has_permission(user) or any( + [scene.user_has_view_permission(user) for scene in self.scenes.all()] + ) + class OriginalImage(OriginalMedia): def media_type(self) -> str: @@ -247,7 +262,9 @@ class Scene(models.Model): default_x = models.FloatField(default=0.0) default_y = models.FloatField(default=0.0) default_z = models.FloatField(default=0.0) - category = models.ForeignKey(Category, related_name="scenes", on_delete=models.SET_NULL, null=True) + category = models.ForeignKey( + Category, related_name="scenes", on_delete=models.SET_NULL, null=True + ) public = models.BooleanField(default=True) @property @@ -255,11 +272,14 @@ class Scene(models.Model): return self.base_content.resolutions.order_by("width").first() def user_has_permission(self, user): - return ( + return user.is_authenticated and ( user.is_superuser or user.is_staff or (self.category and self.category.user_has_permission(user)) ) + def user_has_view_permission(self, user): + return self.public or (user.is_authenticated and self.user_has_permission(user)) + def __str__(self): return self.title diff --git a/quackscape/tours/serializers.py b/quackscape/tours/serializers.py index e6dae2a..81ae034 100644 --- a/quackscape/tours/serializers.py +++ b/quackscape/tours/serializers.py @@ -9,7 +9,7 @@ from .models import ( TeleportElement, TextElement, ImageElement, - Category + Category, ) @@ -31,7 +31,7 @@ class TeleportElementSerializer(serializers.ModelSerializer): "destination_y", "destination_z", "thetaStart", - "thetaLength" + "thetaLength", ] @@ -76,6 +76,13 @@ class OriginalMediaSerializer(serializers.ModelSerializer): model = OriginalMedia fields = ["id", "title", "media_type", "width", "height", "resolutions"] + def to_representation(self, instance): + request = self.context.get("request") + if request and instance.user_has_view_permission(request.user): + return super().to_representation(instance) + else: + return None + class SceneSerializer(serializers.ModelSerializer): base_content = OriginalMediaSerializer() @@ -85,6 +92,13 @@ class SceneSerializer(serializers.ModelSerializer): model = Scene fields = ["id", "title", "description", "base_content", "elements", "category"] + def to_representation(self, instance): + request = self.context.get("request") + if request and instance.user_has_view_permission(request.user): + return super().to_representation(instance) + else: + return None + class CategorySerializer(serializers.ModelSerializer): media = OriginalMediaSerializer(many=True, read_only=True) @@ -92,4 +106,10 @@ class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category - fields = ["id", "title", "media", "scenes"] \ No newline at end of file + fields = ["id", "title", "media", "scenes"] + + def to_representation(self, instance): + ret = super().to_representation(instance) + ret['media'] = [m for m in ret['media'] if m is not None] + ret['scenes'] = [s for s in ret['scenes'] if s is not None] + return ret \ No newline at end of file diff --git a/quackscape/tours/views.py b/quackscape/tours/views.py index c0221f8..3c4823b 100644 --- a/quackscape/tours/views.py +++ b/quackscape/tours/views.py @@ -6,7 +6,11 @@ from django.core.exceptions import PermissionDenied from rest_framework import viewsets from .models import Scene, Element, Category -from .serializers import SceneSerializer, ElementPolymorphicSerializer, CategorySerializer +from .serializers import ( + SceneSerializer, + ElementPolymorphicSerializer, + CategorySerializer, +) class UserPermissionMixin: @@ -33,24 +37,10 @@ class SceneAPIViewSet(viewsets.GenericViewSet, viewsets.mixins.RetrieveModelMixi serializer_class = SceneSerializer queryset = Scene.objects.all() - def get_object(self): - obj = super().get_object() - - if not (obj.public or obj.user_has_permission(self.request.user)): - raise PermissionDenied() - - return obj - class ElementAPIViewSet(viewsets.ModelViewSet): serializer_class = ElementPolymorphicSerializer - - def get_queryset(self): - scene = Scene.objects.get(id=self.kwargs["scene"]) - if not scene.user_has_permission(self.request.user): - raise PermissionDenied - - return scene.elements + queryset = Element.objects.all() class SceneView(PublicPermissionMixin, DetailView): @@ -64,14 +54,6 @@ class SceneEditView(UserPermissionMixin, DetailView): context_object_name = "scene" template_name = "tours/scene_edit.html" - def get_object(self, queryset=None): - obj = super().get_object(queryset) - - if not obj.user_has_permission(self.request.user): - raise PermissionDenied() - - return obj - @method_decorator(xframe_options_exempt, name="dispatch") class SceneEmbedView(SceneView): @@ -81,12 +63,3 @@ class SceneEmbedView(SceneView): class CategoryAPIViewSet(viewsets.ModelViewSet): serializer_class = CategorySerializer queryset = Category.objects.all() - - def get_queryset(self): - categories = Category.objects.all() - - for category in categories: - if not category.user_has_permission(self.request.user): - categories = categories.exclude(id=category.id) - - return categories \ No newline at end of file