Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Market from Sublet #315

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
Empty file added backend/market/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions backend/market/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.contrib import admin
from django.utils.html import mark_safe

Check warning on line 2 in backend/market/admin.py

View check run for this annotation

Codecov / codecov/patch

backend/market/admin.py#L1-L2

Added lines #L1 - L2 were not covered by tests

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

Check warning on line 4 in backend/market/admin.py

View check run for this annotation

Codecov / codecov/patch

backend/market/admin.py#L4

Added line #L4 was not covered by tests


class SubletAdmin(admin.ModelAdmin):
def image_tag(self, instance):
images = ['<img src="%s" height="150" />' for image in instance.images.all()]
return mark_safe("<br>".join(images))

Check warning on line 10 in backend/market/admin.py

View check run for this annotation

Codecov / codecov/patch

backend/market/admin.py#L7-L10

Added lines #L7 - L10 were not covered by tests

image_tag.short_description = "Sublet Images"
readonly_fields = ("image_tag",)

Check warning on line 13 in backend/market/admin.py

View check run for this annotation

Codecov / codecov/patch

backend/market/admin.py#L12-L13

Added lines #L12 - L13 were not covered by tests


admin.site.register(Offer)
admin.site.register(Amenity)
admin.site.register(Sublet, SubletAdmin)
admin.site.register(SubletImage)

Check warning on line 19 in backend/market/admin.py

View check run for this annotation

Codecov / codecov/patch

backend/market/admin.py#L16-L19

Added lines #L16 - L19 were not covered by tests
6 changes: 6 additions & 0 deletions backend/market/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class MarketConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "market"
Empty file.
58 changes: 58 additions & 0 deletions backend/market/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from django.contrib.auth import get_user_model
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField

Check warning on line 3 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L1-L3

Added lines #L1 - L3 were not covered by tests


User = get_user_model()

Check warning on line 6 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L6

Added line #L6 was not covered by tests


class Offer(models.Model):
class Meta:
constraints = [models.UniqueConstraint(fields=["user", "sublet"], name="unique_offer")]

Check warning on line 11 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L9-L11

Added lines #L9 - L11 were not covered by tests

user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="offers_made")
sublet = models.ForeignKey("Sublet", on_delete=models.CASCADE, related_name="offers")
email = models.EmailField(max_length=255, null=True, blank=True)
phone_number = PhoneNumberField(null=True, blank=True)
message = models.CharField(max_length=255, blank=True)
created_date = models.DateTimeField(auto_now_add=True)

Check warning on line 18 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L13-L18

Added lines #L13 - L18 were not covered by tests

def __str__(self):
return f"Offer for {self.sublet} made by {self.user}"

Check warning on line 21 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L20-L21

Added lines #L20 - L21 were not covered by tests


class Amenity(models.Model):
name = models.CharField(max_length=255, primary_key=True)

Check warning on line 25 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L24-L25

Added lines #L24 - L25 were not covered by tests

def __str__(self):
return self.name

Check warning on line 28 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L27-L28

Added lines #L27 - L28 were not covered by tests


class Sublet(models.Model):
subletter = models.ForeignKey(User, on_delete=models.CASCADE)
sublettees = models.ManyToManyField(

Check warning on line 33 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L31-L33

Added lines #L31 - L33 were not covered by tests
User, through=Offer, related_name="sublets_offered", blank=True
)
favorites = models.ManyToManyField(User, related_name="sublets_favorited", blank=True)
amenities = models.ManyToManyField(Amenity, blank=True)

Check warning on line 37 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L36-L37

Added lines #L36 - L37 were not covered by tests

title = models.CharField(max_length=255)
address = models.CharField(max_length=255, null=True, blank=True)
beds = models.IntegerField(null=True, blank=True)
baths = models.DecimalField(max_digits=3, decimal_places=1, null=True, blank=True)
description = models.TextField(null=True, blank=True)
external_link = models.URLField(max_length=255, null=True, blank=True)
price = models.IntegerField()
negotiable = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
start_date = models.DateField()
end_date = models.DateField()

Check warning on line 50 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L39-L50

Added lines #L39 - L50 were not covered by tests

def __str__(self):
return f"{self.title} by {self.subletter}"

Check warning on line 53 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L52-L53

Added lines #L52 - L53 were not covered by tests


class SubletImage(models.Model):
sublet = models.ForeignKey(Sublet, on_delete=models.CASCADE, related_name="images")
image = models.ImageField(upload_to="sublet/images")

Check warning on line 58 in backend/market/models.py

View check run for this annotation

Codecov / codecov/patch

backend/market/models.py#L56-L58

Added lines #L56 - L58 were not covered by tests
57 changes: 57 additions & 0 deletions backend/market/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from rest_framework import permissions

Check warning on line 1 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L1

Added line #L1 was not covered by tests


class IsSuperUser(permissions.BasePermission):

Check warning on line 4 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L4

Added line #L4 was not covered by tests
"""
Grants permission if the current user is a superuser.
"""

def has_object_permission(self, request, view, obj):
return request.user.is_superuser

Check warning on line 10 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L9-L10

Added lines #L9 - L10 were not covered by tests

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

Check warning on line 13 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L12-L13

Added lines #L12 - L13 were not covered by tests


class SubletOwnerPermission(permissions.BasePermission):

Check warning on line 16 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L16

Added line #L16 was not covered by tests
"""
Custom permission to allow the owner of a Sublet to edit or delete it.
"""

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

Check warning on line 22 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L21-L22

Added lines #L21 - L22 were not covered by tests

def has_object_permission(self, request, view, obj):

Check warning on line 24 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L24

Added line #L24 was not covered by tests
# Check if the user is the owner of the Sublet.
if request.method in permissions.SAFE_METHODS:
return True
return obj.subletter == request.user

Check warning on line 28 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L26-L28

Added lines #L26 - L28 were not covered by tests


class SubletImageOwnerPermission(permissions.BasePermission):

Check warning on line 31 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L31

Added line #L31 was not covered by tests
"""
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

Check warning on line 37 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L36-L37

Added lines #L36 - L37 were not covered by tests

def has_object_permission(self, request, view, obj):

Check warning on line 39 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L39

Added line #L39 was not covered by tests
# Check if the user is the owner of the Sublet.
return request.method in permissions.SAFE_METHODS or obj.sublet.subletter == request.user

Check warning on line 41 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L41

Added line #L41 was not covered by tests


class OfferOwnerPermission(permissions.BasePermission):

Check warning on line 44 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L44

Added line #L44 was not covered by tests
"""
Custom permission to allow owner of an offer to delete it.
"""

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

Check warning on line 50 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L49-L50

Added lines #L49 - L50 were not covered by tests

def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:

Check warning on line 53 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L52-L53

Added lines #L52 - L53 were not covered by tests
# Check if the user owns the sublet when getting list
return obj.subletter == request.user

Check warning on line 55 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L55

Added line #L55 was not covered by tests
# This is redundant, here for safety
return obj.user == request.user

Check warning on line 57 in backend/market/permissions.py

View check run for this annotation

Codecov / codecov/patch

backend/market/permissions.py#L57

Added line #L57 was not covered by tests
189 changes: 189 additions & 0 deletions backend/market/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
from phonenumber_field.serializerfields import PhoneNumberField
from profanity_check import predict
from rest_framework import serializers

Check warning on line 3 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L1-L3

Added lines #L1 - L3 were not covered by tests

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

Check warning on line 5 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L5

Added line #L5 was not covered by tests


class AmenitySerializer(serializers.ModelSerializer):
class Meta:
model = Amenity
fields = "__all__"

Check warning on line 11 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L8-L11

Added lines #L8 - L11 were not covered by tests


class OfferSerializer(serializers.ModelSerializer):
phone_number = PhoneNumberField()

Check warning on line 15 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L14-L15

Added lines #L14 - L15 were not covered by tests

class Meta:
model = Offer
fields = "__all__"
read_only_fields = ["id", "created_date", "user"]

Check warning on line 20 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L17-L20

Added lines #L17 - L20 were not covered by tests

def create(self, validated_data):
validated_data["user"] = self.context["request"].user
return super().create(validated_data)

Check warning on line 24 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L22-L24

Added lines #L22 - L24 were not covered by tests


# Create/Update Image Serializer
class SubletImageSerializer(serializers.ModelSerializer):
image = serializers.ImageField(write_only=True, required=False, allow_null=True)

Check warning on line 29 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L28-L29

Added lines #L28 - L29 were not covered by tests

class Meta:
model = SubletImage
fields = ["sublet", "image"]

Check warning on line 33 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L31-L33

Added lines #L31 - L33 were not covered by tests


# Browse images
class SubletImageURLSerializer(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField("get_image_url")

Check warning on line 38 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L37-L38

Added lines #L37 - L38 were not covered by tests

def get_image_url(self, obj):
image = obj.image

Check warning on line 41 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L40-L41

Added lines #L40 - L41 were not covered by tests

if not image:
return None
if image.url.startswith("http"):
return image.url
elif "request" in self.context:
return self.context["request"].build_absolute_uri(image.url)

Check warning on line 48 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L43-L48

Added lines #L43 - L48 were not covered by tests
else:
return image.url

Check warning on line 50 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L50

Added line #L50 was not covered by tests

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

Check warning on line 54 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L52-L54

Added lines #L52 - L54 were not covered by tests


# complex sublet serializer for use in C/U/D + getting info about a singular sublet
class SubletSerializer(serializers.ModelSerializer):

Check warning on line 58 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L58

Added line #L58 was not covered by tests
# amenities = AmenitySerializer(many=True, required=False)
# images = SubletImageURLSerializer(many=True, required=False)
amenities = serializers.PrimaryKeyRelatedField(

Check warning on line 61 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L61

Added line #L61 was not covered by tests
many=True, queryset=Amenity.objects.all(), required=False
)

class Meta:
model = Sublet
read_only_fields = [

Check warning on line 67 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L65-L67

Added lines #L65 - L67 were not covered by tests
"id",
"created_at",
"subletter",
"sublettees",
# "images"
]
fields = [

Check warning on line 74 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L74

Added line #L74 was not covered by tests
"id",
"subletter",
"amenities",
"title",
"address",
"beds",
"baths",
"description",
"external_link",
"price",
"negotiable",
"start_date",
"end_date",
"expires_at",
# "images",
# images are now created/deleted through a separate endpoint (see urls.py)
# this serializer isn't used for getting,
# but gets on sublets will include ids/urls for images
]

def validate_title(self, value):
if self.contains_profanity(value):
raise serializers.ValidationError("The title contains inappropriate language.")
return value

Check warning on line 98 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L95-L98

Added lines #L95 - L98 were not covered by tests

def validate_description(self, value):
if self.contains_profanity(value):
raise serializers.ValidationError("The description contains inappropriate language.")
return value

Check warning on line 103 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L100-L103

Added lines #L100 - L103 were not covered by tests

def contains_profanity(self, text):
return predict([text])[0]

Check warning on line 106 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L105-L106

Added lines #L105 - L106 were not covered by tests

def create(self, validated_data):
validated_data["subletter"] = self.context["request"].user
instance = super().create(validated_data)
instance.save()
return instance

Check warning on line 112 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L108-L112

Added lines #L108 - L112 were not covered by tests

# delete_images is a list of image ids to delete
def update(self, instance, validated_data):

Check warning on line 115 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L115

Added line #L115 was not covered by tests
# Check if the user is the subletter before allowing the update
if (

Check warning on line 117 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L117

Added line #L117 was not covered by tests
self.context["request"].user == instance.subletter
or self.context["request"].user.is_superuser
):
instance = super().update(instance, validated_data)
instance.save()
return instance

Check warning on line 123 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L121-L123

Added lines #L121 - L123 were not covered by tests
else:
raise serializers.ValidationError("You do not have permission to update this sublet.")

Check warning on line 125 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L125

Added line #L125 was not covered by tests

def destroy(self, instance):

Check warning on line 127 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L127

Added line #L127 was not covered by tests
# Check if the user is the subletter before allowing the delete
if (

Check warning on line 129 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L129

Added line #L129 was not covered by tests
self.context["request"].user == instance.subletter
or self.context["request"].user.is_superuser
):
instance.delete()

Check warning on line 133 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L133

Added line #L133 was not covered by tests
else:
raise serializers.ValidationError("You do not have permission to delete this sublet.")

Check warning on line 135 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L135

Added line #L135 was not covered by tests


class SubletSerializerRead(serializers.ModelSerializer):
amenities = serializers.PrimaryKeyRelatedField(

Check warning on line 139 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L138-L139

Added lines #L138 - L139 were not covered by tests
many=True, queryset=Amenity.objects.all(), required=False
)
images = SubletImageURLSerializer(many=True, required=False)

Check warning on line 142 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L142

Added line #L142 was not covered by tests

class Meta:
model = Sublet
read_only_fields = ["id", "created_at", "subletter", "sublettees"]
fields = [

Check warning on line 147 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L144-L147

Added lines #L144 - L147 were not covered by tests
"id",
"subletter",
"amenities",
"title",
"address",
"beds",
"baths",
"description",
"external_link",
"price",
"negotiable",
"start_date",
"end_date",
"expires_at",
"images",
]


# simple sublet serializer for use when pulling all serializers/etc
class SubletSerializerSimple(serializers.ModelSerializer):
amenities = serializers.PrimaryKeyRelatedField(

Check warning on line 168 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L167-L168

Added lines #L167 - L168 were not covered by tests
many=True, queryset=Amenity.objects.all(), required=False
)
images = SubletImageURLSerializer(many=True, required=False)

Check warning on line 171 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L171

Added line #L171 was not covered by tests

class Meta:
model = Sublet
fields = [

Check warning on line 175 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L173-L175

Added lines #L173 - L175 were not covered by tests
"id",
"subletter",
"amenities",
"title",
"address",
"beds",
"baths",
"price",
"negotiable",
"start_date",
"end_date",
"images",
]
read_only_fields = ["id", "subletter"]

Check warning on line 189 in backend/market/serializers.py

View check run for this annotation

Codecov / codecov/patch

backend/market/serializers.py#L189

Added line #L189 was not covered by tests
49 changes: 49 additions & 0 deletions backend/market/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from django.urls import path
from rest_framework import routers

Check warning on line 2 in backend/market/urls.py

View check run for this annotation

Codecov / codecov/patch

backend/market/urls.py#L1-L2

Added lines #L1 - L2 were not covered by tests

from market.views import (

Check warning on line 4 in backend/market/urls.py

View check run for this annotation

Codecov / codecov/patch

backend/market/urls.py#L4

Added line #L4 was not covered by tests
Amenities,
CreateImages,
DeleteImage,
Favorites,
Offers,
Properties,
UserFavorites,
UserOffers,
)


app_name = "sublet"

Check warning on line 16 in backend/market/urls.py

View check run for this annotation

Codecov / codecov/patch

backend/market/urls.py#L16

Added line #L16 was not covered by tests

router = routers.DefaultRouter()
router.register(r"properties", Properties, basename="properties")

Check warning on line 19 in backend/market/urls.py

View check run for this annotation

Codecov / codecov/patch

backend/market/urls.py#L18-L19

Added lines #L18 - L19 were not covered by tests

additional_urls = [

Check warning on line 21 in backend/market/urls.py

View check run for this annotation

Codecov / codecov/patch

backend/market/urls.py#L21

Added line #L21 was not covered by tests
# List of all amenities
path("amenities/", Amenities.as_view(), name="amenities"),
# All favorites for user
path("favorites/", UserFavorites.as_view(), name="user-favorites"),
# All offers made by user
path("offers/", UserOffers.as_view(), name="user-offers"),
# Favorites
# post: add a sublet to the user's favorites
# delete: remove a sublet from the user's favorites
path(
"properties/<sublet_id>/favorites/",
Favorites.as_view({"post": "create", "delete": "destroy"}),
),
# Offers
# get: list all offers for a sublet
# post: create an offer for a sublet
# delete: delete an offer for a sublet
path(
"properties/<sublet_id>/offers/",
Offers.as_view({"get": "list", "post": "create", "delete": "destroy"}),
),
# Image Creation
path("properties/<sublet_id>/images/", CreateImages.as_view()),
# Image Deletion
path("properties/images/<image_id>/", DeleteImage.as_view()),
]

urlpatterns = router.urls + additional_urls

Check warning on line 49 in backend/market/urls.py

View check run for this annotation

Codecov / codecov/patch

backend/market/urls.py#L49

Added line #L49 was not covered by tests
Loading
Loading