Skip to content

Commit

Permalink
Sublet Image Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-Jess committed Dec 4, 2023
1 parent 90eac30 commit 2497707
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 25 deletions.
15 changes: 15 additions & 0 deletions backend/sublet/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ def has_object_permission(self, request, view, obj):
return obj.subletter == request.user


class SubletImageOwnerPermission(permissions.BasePermission):
"""
Custom permission to allow the owner of a SubletImage to edit or delete it.
"""

def has_permission(self, request, view):
return request.user.is_authenticated

def has_object_permission(self, request, view, obj):
# Check if the user is the owner of the Sublet.
if request.method in permissions.SAFE_METHODS:
return True
return obj.sublet.subletter == request.user


class OfferOwnerPermission(permissions.BasePermission):
"""
Custom permission to allow owner of an offer to delete it.
Expand Down
102 changes: 84 additions & 18 deletions backend/sublet/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from phonenumber_field.serializerfields import PhoneNumberField
from rest_framework import serializers
from rest_framework.generics import get_object_or_404

from sublet.models import Amenity, Offer, Sublet, SubletImage

Expand Down Expand Up @@ -30,7 +31,6 @@ class SubletImageSerializer(serializers.ModelSerializer):
class Meta:
model = SubletImage
fields = ["sublet", "image"]
read_only_fields = ["sublet", "image"]


# Browse images
Expand All @@ -51,17 +51,42 @@ def get_image_url(self, obj):

class Meta:
model = SubletImage
fields = ["image_url"]
fields = ["id", "image_url"]


# complex sublet serializer for use in C/U/D + getting info about a singular sublet
class SubletSerializer(serializers.ModelSerializer):
amenities = AmenitySerializer(many=True, required=False)
images = serializers.ListField(
child=serializers.FileField(max_length=100000, allow_empty_file=False, use_url=False),
required=False,
write_only=True,
)
delete_images = serializers.ListField(
child=serializers.IntegerField(), required=False, write_only=True
)

class Meta:
model = Sublet
exclude = ["favorites"]
read_only_fields = ["id", "created_at", "subletter", "sublettees"]
fields = [
"id",
"subletter",
"amenities",
"title",
"address",
"beds",
"baths",
"description",
"external_link",
"min_price",
"max_price",
"start_date",
"end_date",
"expires_at",
"images",
"delete_images",
]

def parse_amenities(self, raw_amenities):
if isinstance(raw_amenities, list):
Expand All @@ -74,29 +99,44 @@ def parse_amenities(self, raw_amenities):

def create(self, validated_data):
validated_data["subletter"] = self.context["request"].user
images = validated_data.pop("images")
instance = super().create(validated_data)
data = self.context["request"].POST
amenities = self.parse_amenities(data.getlist("amenities"))
instance.amenities.set(amenities)
instance.save()
# TODO: make this atomic
for img in images:
img_serializer = SubletImageSerializer(data={"sublet": instance.id, "image": img})
img_serializer.is_valid(raise_exception=True)
img_serializer.save()
return instance

# delete_images is a list of image ids to delete
def update(self, instance, validated_data):
# Check if the user is the subletter before allowing the update
if (
self.context["request"].user == instance.subletter
or self.context["request"].user.is_superuser
):
amenities_data = self.context["request"].data
if amenities_data.get("amenities") is not None:
amenities = self.parse_amenities(amenities_data.getlist("amenities"))
instance.amenities.set(amenities)
validated_data.pop("amenities", None)
instance = super().update(instance, validated_data)
instance.save()
else:
raise serializers.ValidationError("You do not have permission to update this sublet.")

# This is probably redundant given permissions classes?
# if (
# self.context["request"].user == instance.subletter
# or self.context["request"].user.is_superuser
# ):
amenities_data = self.context["request"].data
if amenities_data.get("amenities") is not None:
amenities = self.parse_amenities(amenities_data["amenities"])
instance.amenities.set(amenities)
validated_data.pop("amenities", None)
delete_images = validated_data.pop("delete_images")
instance = super().update(instance, validated_data)
instance.save()
existing_images = Sublet.objects.get(id=instance.id).images.all()
print(existing_images)
for img in delete_images:
get_object_or_404(existing_images, id=img)
# this should probably be atomic
for img in delete_images:
existing_images.get(id=img).delete()
# else:
# raise serializers.ValidationError("You do not have permission to update this sublet.")
return instance

def destroy(self, instance):
Expand All @@ -110,8 +150,34 @@ def destroy(self, instance):
raise serializers.ValidationError("You do not have permission to delete this sublet.")


class SubletSerializerRead(serializers.ModelSerializer):
amenities = AmenitySerializer(many=True, required=False)
images = SubletImageURLSerializer(many=True, required=False)

class Meta:
model = Sublet
read_only_fields = ["id", "created_at", "subletter", "sublettees"]
fields = [
"id",
"subletter",
"amenities",
"title",
"address",
"beds",
"baths",
"description",
"external_link",
"min_price",
"max_price",
"start_date",
"end_date",
"expires_at",
"images",
]


# simple sublet serializer for use when pulling all serializers/etc
class SimpleSubletSerializer(serializers.ModelSerializer):
class SubletSerializerSimple(serializers.ModelSerializer):
amenities = AmenitySerializer(many=True, required=False)
images = SubletImageURLSerializer(many=True, required=False)

Expand Down
3 changes: 2 additions & 1 deletion backend/sublet/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.urls import path
from rest_framework import routers

from sublet.views import Amenities, Favorites, Offers, Properties, UserFavorites, UserOffers
from sublet.views import Amenities, Favorites, Offers, Properties, UserFavorites, UserOffers, Images


app_name = "sublet"
Expand All @@ -21,6 +21,7 @@
"properties/<sublet_id>/offers/",
Offers.as_view({"get": "list", "post": "create", "delete": "destroy"}),
),
path("properties/<sublet_id>/images/", Images.as_view()),
]

urlpatterns = router.urls + additional_urls
75 changes: 69 additions & 6 deletions backend/sublet/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
from django.contrib.auth import get_user_model
from django.db.models import prefetch_related_objects
from django.utils import timezone
from rest_framework import exceptions, generics, mixins, status, viewsets
from rest_framework.generics import get_object_or_404
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from sublet.models import Amenity, Offer, Sublet
from sublet.permissions import IsSuperUser, OfferOwnerPermission, SubletOwnerPermission
from sublet.models import Amenity, Offer, Sublet, SubletImage
from sublet.permissions import (
IsSuperUser,
OfferOwnerPermission,
SubletOwnerPermission,
SubletImageOwnerPermission,
)
from sublet.serializers import (
AmenitySerializer,
OfferSerializer,
SimpleSubletSerializer,
SubletSerializerSimple,
SubletSerializer,
SubletSerializerRead,
SubletImageSerializer,
)


Expand All @@ -29,7 +38,7 @@ def get(self, request, *args, **kwargs):


class UserFavorites(generics.ListAPIView):
serializer_class = SimpleSubletSerializer
serializer_class = SubletSerializerSimple
permission_classes = [IsAuthenticated]

def get_queryset(self):
Expand Down Expand Up @@ -62,11 +71,40 @@ class Properties(viewsets.ModelViewSet):
"""

permission_classes = [SubletOwnerPermission | IsSuperUser]
serializer_class = SubletSerializer
parser_classes = (MultiPartParser, FormParser, JSONParser)

def get_serializer_class(self):
if self.action == "retrieve":
return SubletSerializerRead
return SubletSerializer

def get_queryset(self):
return Sublet.objects.all()

def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # Check if the data is valid
instance = serializer.save() # Create the Sublet
instance_serializer = SubletSerializerRead(instance=instance, context={"request": request})
return Response(instance_serializer.data, status=status.HTTP_201_CREATED)

def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)

queryset = self.filter_queryset(self.get_queryset())
# no clue what this does but I copied it from the DRF source code
if queryset._prefetch_related_lookups:
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance,
# and then re-prefetch related objects
instance._prefetched_objects_cache = {}
prefetch_related_objects([instance], *queryset._prefetch_related_lookups)
return Response(SubletSerializerRead(instance=instance).data)

# This is currently redundant but will leave for use when implementing image creation
# def create(self, request, *args, **kwargs):
# # amenities = request.data.pop("amenities", [])
Expand Down Expand Up @@ -132,10 +170,35 @@ def list(self, request, *args, **kwargs):
queryset = queryset.filter(baths=baths)

# Serialize and return the queryset
serializer = SimpleSubletSerializer(queryset, many=True)
serializer = SubletSerializerSimple(queryset, many=True)
return Response(serializer.data)


class Images(generics.CreateAPIView):
serializer_class = SubletImageSerializer
http_method_names = ["post"]
permission_classes = [SubletImageOwnerPermission | IsSuperUser]
parser_classes = (
MultiPartParser,
FormParser,
)

def get_queryset(self, *args, **kwargs):
get_object_or_404(Sublet, id=int(self.kwargs["sublet_id"]))
return SubletImage.objects.filter(sublet_id=int(self.kwargs["sublet_id"]))

# takes an image multipart form data and creates a new image object
def post(self, request, *args, **kwargs):
images = request.data.getlist("images")
sublet_id = int(self.kwargs["sublet_id"])
self.get_queryset() # check if sublet exists
for img in images:

This comment has been minimized.

Copy link
@dr-Jess

dr-Jess Dec 4, 2023

Author Contributor

probably make this atomic too ideally oops

img_serializer = self.get_serializer(data={"sublet": sublet_id, "image": img})
img_serializer.is_valid(raise_exception=True)
img_serializer.save()
return Response(status=status.HTTP_201_CREATED)


class Favorites(mixins.DestroyModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = SubletSerializer
http_method_names = ["post", "delete"]
Expand Down

0 comments on commit 2497707

Please sign in to comment.