-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
678 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
from sublet.models import Amenity, Offer, Sublet, SubletImage | ||
|
||
|
||
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)) | ||
|
||
image_tag.short_description = "Sublet Images" | ||
readonly_fields = ("image_tag",) | ||
|
||
|
||
admin.site.register(Offer) | ||
admin.site.register(Amenity) | ||
admin.site.register(Sublet, SubletAdmin) | ||
admin.site.register(SubletImage) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
class Offer(models.Model): | ||
class Meta: | ||
constraints = [models.UniqueConstraint(fields=["user", "sublet"], name="unique_offer")] | ||
|
||
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) | ||
|
||
def __str__(self): | ||
return f"Offer for {self.sublet} made by {self.user}" | ||
|
||
|
||
class Amenity(models.Model): | ||
name = models.CharField(max_length=255, primary_key=True) | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
|
||
class Sublet(models.Model): | ||
subletter = models.ForeignKey(User, on_delete=models.CASCADE) | ||
sublettees = models.ManyToManyField( | ||
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) | ||
|
||
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() | ||
|
||
def __str__(self): | ||
return f"{self.title} by {self.subletter}" | ||
|
||
|
||
class SubletImage(models.Model): | ||
sublet = models.ForeignKey(Sublet, on_delete=models.CASCADE, related_name="images") | ||
image = models.ImageField(upload_to="sublet/images") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from rest_framework import permissions | ||
|
||
|
||
class IsSuperUser(permissions.BasePermission): | ||
""" | ||
Grants permission if the current user is a superuser. | ||
""" | ||
|
||
def has_object_permission(self, request, view, obj): | ||
return request.user.is_superuser | ||
|
||
def has_permission(self, request, view): | ||
return request.user.is_superuser | ||
|
||
|
||
class SubletOwnerPermission(permissions.BasePermission): | ||
""" | ||
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 | ||
|
||
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.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. | ||
return request.method in permissions.SAFE_METHODS or obj.sublet.subletter == request.user | ||
|
||
|
||
class OfferOwnerPermission(permissions.BasePermission): | ||
""" | ||
Custom permission to allow owner of an offer to delete it. | ||
""" | ||
|
||
def has_permission(self, request, view): | ||
return request.user.is_authenticated | ||
|
||
def has_object_permission(self, request, view, obj): | ||
if request.method in permissions.SAFE_METHODS: | ||
# Check if the user owns the sublet when getting list | ||
return obj.subletter == request.user | ||
# This is redundant, here for safety | ||
return obj.user == request.user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
from sublet.models import Amenity, Offer, Sublet, SubletImage | ||
|
||
|
||
class AmenitySerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Amenity | ||
fields = "__all__" | ||
|
||
|
||
class OfferSerializer(serializers.ModelSerializer): | ||
phone_number = PhoneNumberField() | ||
|
||
class Meta: | ||
model = Offer | ||
fields = "__all__" | ||
read_only_fields = ["id", "created_date", "user"] | ||
|
||
def create(self, validated_data): | ||
validated_data["user"] = self.context["request"].user | ||
return super().create(validated_data) | ||
|
||
|
||
# Create/Update Image Serializer | ||
class SubletImageSerializer(serializers.ModelSerializer): | ||
image = serializers.ImageField(write_only=True, required=False, allow_null=True) | ||
|
||
class Meta: | ||
model = SubletImage | ||
fields = ["sublet", "image"] | ||
|
||
|
||
# Browse images | ||
class SubletImageURLSerializer(serializers.ModelSerializer): | ||
image_url = serializers.SerializerMethodField("get_image_url") | ||
|
||
def get_image_url(self, obj): | ||
image = obj.image | ||
|
||
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) | ||
else: | ||
return image.url | ||
|
||
class Meta: | ||
model = SubletImage | ||
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 = SubletImageURLSerializer(many=True, required=False) | ||
amenities = serializers.PrimaryKeyRelatedField( | ||
many=True, queryset=Amenity.objects.all(), required=False | ||
) | ||
|
||
class Meta: | ||
model = Sublet | ||
read_only_fields = [ | ||
"id", | ||
"created_at", | ||
"subletter", | ||
"sublettees", | ||
# "images" | ||
] | ||
fields = [ | ||
"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 | ||
|
||
def validate_description(self, value): | ||
if self.contains_profanity(value): | ||
raise serializers.ValidationError("The description contains inappropriate language.") | ||
return value | ||
|
||
def contains_profanity(self, text): | ||
return predict([text])[0] | ||
|
||
def create(self, validated_data): | ||
validated_data["subletter"] = self.context["request"].user | ||
instance = super().create(validated_data) | ||
instance.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 | ||
): | ||
instance = super().update(instance, validated_data) | ||
instance.save() | ||
return instance | ||
else: | ||
raise serializers.ValidationError("You do not have permission to update this sublet.") | ||
|
||
def destroy(self, instance): | ||
# Check if the user is the subletter before allowing the delete | ||
if ( | ||
self.context["request"].user == instance.subletter | ||
or self.context["request"].user.is_superuser | ||
): | ||
instance.delete() | ||
else: | ||
raise serializers.ValidationError("You do not have permission to delete this sublet.") | ||
|
||
|
||
class SubletSerializerRead(serializers.ModelSerializer): | ||
amenities = serializers.PrimaryKeyRelatedField( | ||
many=True, queryset=Amenity.objects.all(), 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", | ||
"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( | ||
many=True, queryset=Amenity.objects.all(), required=False | ||
) | ||
images = SubletImageURLSerializer(many=True, required=False) | ||
|
||
class Meta: | ||
model = Sublet | ||
fields = [ | ||
"id", | ||
"subletter", | ||
"amenities", | ||
"title", | ||
"address", | ||
"beds", | ||
"baths", | ||
"price", | ||
"negotiable", | ||
"start_date", | ||
"end_date", | ||
"images", | ||
] | ||
read_only_fields = ["id", "subletter"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from django.urls import path | ||
from rest_framework import routers | ||
|
||
from sublet.views import ( | ||
Amenities, | ||
CreateImages, | ||
DeleteImage, | ||
Favorites, | ||
Offers, | ||
Properties, | ||
UserFavorites, | ||
UserOffers, | ||
) | ||
|
||
|
||
app_name = "sublet" | ||
|
||
router = routers.DefaultRouter() | ||
router.register(r"properties", Properties, basename="properties") | ||
|
||
additional_urls = [ | ||
# 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 |
Oops, something went wrong.