Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions backend/market/migrations/0006_rename_favorites_to_saved.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("market", "0005_sublet_true_latitude_sublet_true_longitude"),
]

operations = [
migrations.RenameField(
model_name="listing",
old_name="favorites",
new_name="saved",
),
migrations.AlterField(
model_name="listing",
name="saved",
field=models.ManyToManyField(
blank=True, related_name="listings_saved", to=settings.AUTH_USER_MODEL
),
),
]
8 changes: 4 additions & 4 deletions backend/market/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Meta:
def __str__(self):
return f"Offer for {self.listing} made by {self.user}"


class Category(models.Model):
name = models.CharField(max_length=100, unique=True)

Expand Down Expand Up @@ -81,9 +82,7 @@ class Meta:
User, through=Offer, related_name="listings_offered", blank=True
)
tags = models.ManyToManyField(Tag, blank=True)
favorites = models.ManyToManyField(
User, related_name="listings_favorited", blank=True
)
saved = models.ManyToManyField(User, related_name="listings_saved", blank=True)

title = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
Expand Down Expand Up @@ -170,7 +169,8 @@ def _calculate_approximate_location(self, latitude, longitude):
def approximate_location(self):
if self.latitude is not None and self.longitude is not None:
approximate_location = self._calculate_approximate_location(
self.latitude, self.longitude)
self.latitude, self.longitude
)
return approximate_location
return None, None

Expand Down
39 changes: 19 additions & 20 deletions backend/market/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError as ModelValidationError
from profanity_check import predict
Expand Down Expand Up @@ -126,6 +125,7 @@ def get_longitude(self, obj):
return float(approx_lon)
return None


# Unified serializer for all listing types (Items and Sublets); used for CRUD operations
class ListingSerializer(ListingTypeMixin, ModelSerializer):
LISTING_TYPE_CONFIG = {
Expand Down Expand Up @@ -156,7 +156,7 @@ class ListingSerializer(ListingTypeMixin, ModelSerializer):
seller = UserSerializer(read_only=True)
listing_type = SerializerMethodField()
additional_data = SerializerMethodField()
is_favorited = SerializerMethodField()
is_saved = SerializerMethodField()
external_link = URLField(required=False, allow_blank=True, allow_null=True)
negotiable = BooleanField(required=False, default=True)
expires_at = DateTimeField(required=False, allow_null=True)
Expand All @@ -168,7 +168,7 @@ class Meta:
"seller",
"buyers",
"tags",
"favorites",
"saved",
"title",
"description",
"external_link",
Expand All @@ -179,15 +179,15 @@ class Meta:
"images",
"listing_type",
"additional_data",
"is_favorited",
"is_saved",
]
read_only_fields = [
"id",
"created_at",
"seller",
"buyers",
"images",
"favorites",
"saved",
]

def validate(self, attrs):
Expand Down Expand Up @@ -221,11 +221,11 @@ def validate(self, attrs):

return super().validate(attrs)

def get_is_favorited(self, obj):
def get_is_saved(self, obj):
request = self.context.get("request")
if not request or not request.user or not request.user.is_authenticated:
return False
return request.user.listings_favorited.filter(id=obj.id).exists()
return request.user.listings_saved.filter(id=obj.id).exists()

def validate_title(self, value):
if self.contains_profanity(value):
Expand Down Expand Up @@ -291,7 +291,6 @@ def _create_sublet(self, validated_data, additional_data):
latitude = additional_data.get("latitude")
longitude = additional_data.get("longitude")


if latitude is not None:
latitude = float(latitude)
if longitude is not None:
Expand Down Expand Up @@ -374,8 +373,8 @@ def _update_sublet(self, instance, additional_data):
# Read-only serializer for use when reading a single listing
class ListingSerializerPublic(ListingTypeMixin, ModelSerializer):
buyer_count = SerializerMethodField()
favorite_count = SerializerMethodField()
is_favorited = SerializerMethodField()
saved_count = SerializerMethodField()
is_saved = SerializerMethodField()
tags = SlugRelatedField(many=True, slug_field="name", queryset=Tag.objects.all())
images = ListingImageURLSerializer(many=True)
seller = UserSerializer(read_only=True)
Expand All @@ -396,29 +395,29 @@ class Meta:
"negotiable",
"expires_at",
"images",
"favorite_count",
"saved_count",
"listing_type",
"additional_data",
"is_favorited",
"is_saved",
]
read_only_fields = fields

def get_buyer_count(self, obj):
return obj.buyers.count()

def get_favorite_count(self, obj):
return obj.favorites.count()
def get_saved_count(self, obj):
return obj.saved.count()

def get_is_favorited(self, obj):
def get_is_saved(self, obj):
request = self.context.get("request")
if not request or not request.user or not request.user.is_authenticated:
return False
return request.user.listings_favorited.filter(id=obj.id).exists()
return request.user.listings_saved.filter(id=obj.id).exists()


# Read-only serializer for use when pulling all listings /etc
class ListingSerializerList(ListingTypeMixin, ModelSerializer):
favorite_count = SerializerMethodField()
saved_count = SerializerMethodField()
tags = SlugRelatedField(many=True, slug_field="name", queryset=Tag.objects.all())
images = ListingImageURLSerializer(many=True)
seller = UserSerializer(read_only=True)
Expand All @@ -435,11 +434,11 @@ class Meta:
"price",
"expires_at",
"images",
"favorite_count",
"saved_count",
"listing_type",
"additional_data",
]
read_only_fields = fields

def get_favorite_count(self, obj):
return obj.favorites.count()
def get_saved_count(self, obj):
return obj.saved.count()
18 changes: 9 additions & 9 deletions backend/market/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from market.views import (
CreateImages,
DeleteImage,
Favorites,
Listings,
Offers,
OffersMade,
OffersReceived,
SavedListings,
Tags,
UserFavorites,
UserSavedListings,
get_current_user,
get_phone_status,
send_verification_code,
Expand All @@ -28,18 +28,18 @@
path("user/me/", get_current_user, name="current-user"),
# List of all amenities
path("tags/", Tags.as_view(), name="tags"),
# All favorites for user
path("favorites/", UserFavorites.as_view(), name="user-favorites"),
# All saved listings for user
path("saved/", UserSavedListings.as_view(), name="user-saved-listings"),
# All offers made by user
path("offers/made/", OffersMade.as_view(), name="offers-made"),
# All offers for an listing owned by user
path("offers/received/", OffersReceived.as_view(), name="offers-received"),
# Favorites
# post: add a listing to the user's favorites
# delete: remove a listing from the user's favorites
# Saved listings
# post: save a listing for the user
# delete: unsave a listing for the user
path(
"listings/<listing_id>/favorites/",
Favorites.as_view({"post": "create", "delete": "destroy"}),
"listings/<listing_id>/saved/",
SavedListings.as_view({"post": "create", "delete": "destroy"}),
),
# Offers
# get: list all offers for an listing
Expand Down
26 changes: 13 additions & 13 deletions backend/market/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ def get_queryset(self):
return Tag.objects.all()


class UserFavorites(ListAPIView, DefaultOrderMixin):
class UserSavedListings(ListAPIView, DefaultOrderMixin):
serializer_class = ListingSerializerList
permission_classes = [IsAuthenticated]
pagination_class = PageSizeOffsetPagination

def get_queryset(self):
user = self.request.user
return user.listings_favorited.all()
return user.listings_saved.all()


# TODO: Can add feature to filter for active offers only
Expand Down Expand Up @@ -245,7 +245,7 @@ def destroy(self, request, *args, **kwargs):

# TODO: We don't use the CreateModelMixin or DestroyModelMixin's functionality here.
# Think about if there's a better way
class Favorites(
class SavedListings(
mixins.DestroyModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet
):
serializer_class = ListingSerializer
Expand All @@ -255,32 +255,32 @@ class Favorites(

def get_queryset(self):
user = self.request.user
return user.listings_favorited.all()
return user.listings_saved.all()

def create(self, request, *args, **kwargs):
listing_id = int(self.kwargs["listing_id"])
favorites = request.user.listings_favorited
if favorites.filter(id=listing_id).exists():
saved = request.user.listings_saved
if saved.filter(id=listing_id).exists():
return Response(
{"liked": True, "detail": "User has already liked the listing"},
{"saved": True, "detail": "User has already saved the listing"},
status=status.HTTP_409_CONFLICT,
)
listing = get_object_or_404(Listing, id=listing_id)
favorites.add(listing)
return Response({"liked": True}, status=status.HTTP_201_CREATED)
saved.add(listing)
return Response({"saved": True}, status=status.HTTP_201_CREATED)

def destroy(self, request, *args, **kwargs):
listing_id = int(self.kwargs["listing_id"])
listing = get_object_or_404(Listing, id=listing_id)

if listing not in request.user.listings_favorited.all():
if listing not in request.user.listings_saved.all():
return Response(
{"liked": False, "detail": "User hasn't liked the listing yet"},
{"saved": False, "detail": "User hasn't saved the listing yet"},
status=status.HTTP_404_NOT_FOUND,
)

request.user.listings_favorited.remove(listing)
return Response({"liked": False}, status=status.HTTP_200_OK)
request.user.listings_saved.remove(listing)
return Response({"saved": False}, status=status.HTTP_200_OK)


class Offers(viewsets.ModelViewSet):
Expand Down
Loading
Loading