Part 1. Learned In Code
# bookings -> serializers.py File
if Booking.objects.filter(check_in__lte=data['check_in'], check_out__gte=data['check_out'],).exists():
# check in과 check out 사이의 예약을 확인할 순 있는데 check_in, out 이후에 존재하는 booking을 확인 못함
# Booking.objects.filter(check_in__gte=data['check_in'], check_out__lte=data['check_out'],).exists()
# bookings -> models.py
check_out = models.DateField(null=True, blank=True,)
# null, blank를 True로 하는 것은 사용될 때도 있고 사용 안될때도 있기 때문
# Categories -> views.py
class CategoryViewSet(ModelViewSet):
# Viewset은 두 가지를 알아야함
# serializer가 뭔지 알아야 하고 Viewset의 object가 뭔지 알아야함, 사용하기에 좋지는 않음
serializer_class = CategorySerializer
queryset = Category.objects.all()
# common -> models.py
# 이 class는 djagno에서 model을 configure할 때 사용함
# abstract model -> django가 이 model을 봐도 db에 저장안함(데이터로 사용x)
class Meta:
abstract = True
# common -> permissions.py
message = "You need to be logged in for this!" # message는 permission이 user를 차단했을때 보임
def has_permission(self, source: typing.Any, info: Info, **kwargs):
return info.context.request.user.is_authenticated # user is authenticated가 True나 False를 반환해줌
# config -> authentications.py
def authenticate(self, request):
username = request.headers.get('Trust-Me') # username이 TrustMe Header에 존재하면 user가 인증을 시도했다는 의미
if not username:
return None
try:
user = User.objects.get(username=username)
return (user, None) # user = request.user
except User.DoesNotExist:
raise AuthenticationFailed(f"No user {username}") # user가 username을 headers에 보냈지만 잘못된 정보임
# config -> settings.py
path("api/v3/categories/", include("categories.urls")), # api를 나타내는 새로운 url 이름을 지어줌(버전까지 나타내주는 것도 중요)
# experiences -> models.py
host = models.ForeignKey("users.User", on_delete=models.CASCADE, related_name="experiences",)
# cascade는 카테고리가 삭제되면 이 experiences도 삭제된다는걸 말함
# SET_NULL은 categories의 category가 삭제되면 experiences의 카테고리를 null로 만듦
category = models.ForeignKey("categories.Category", null=True, blank=True, on_delete=models.SET_NULL, related_name="experiences",)
# medias -> models.py
file = models.URLField() # 파일은 장고에서 손대지 않을 거기 때문에 url필드로 수정함
# medias -> views.py
def delete(self, request, pk):
photo = self.get_object(pk)
# 만약 사진이 방을 가지고 주인이 있고 주인이 요청한 유저와 다르다면 권한 거부
# 사진이 경험을 가지고 있고 경험의 주인과 요청 보낸 유저가 다르다면 권한 거부
if (photo.room and photo.room.owner != request.user) or (photo.experience and photo.experience.host != request.user):
raise PermissionDenied
photo.delete()
return Response(status=HTTP_200_OK)
# 유저가 인증되야 하는 APIView에서 인증을 확인하지 않아도 가능하게 할 수 있음
# reviews -> admin.py
# 커스텀 필터를 만드려면 admin.SimpleListFilter를 상속받는 클래스를 만들어야함
# 2가지를 명시해야함
# parameter_name은 url에 표시됨
class WordFilter(admin.SimpleListFilter):
title = "Filter by words!"
parameter_name = 'word'
# lookups와 queryset 2가지 메소드를 만들어야함
# lookups은 튜플의 리스트를 리턴해야 하는 함수, 튜플의 첫번째 요소는 url에 나타남, 두번째 요소는 유저가 보고 클릭하게 되는 텍스트임
# queryset은 필터링된 review를 리턴해야 하는 메소드
# url에 있는 값을 가져오기 위해 self.value를 호출하기만 하면 됨
def lookups(self, request, model_admin):
return [("good", "Good"), ("great", "Great"), ("awesome","Awesome"),]
def queryset(self, request, reviews):
# url에 보이는 word를 줌
word = self.value()
if word:
# payload에 word를 포함하는 review를 필터링함, word는 url안에 있던 것
return reviews.filter(payload__contains=word)
# 만약 url에 어떠한 단어가 없어도 모든 리뷰를 리턴하도록 해야 함 -? '모두'라는 필터를 선택했을 때 발생
else:
reviews
# reviews -> models.py
# Reviews는 한번의 experience를 가짐
# experience는 많은 reviews를 가질 수 있음
user = models.ForeignKey("users.User", on_delete=models.CASCADE, related_name="reviews",)
room = models.ForeignKey("rooms.Room", null=True, blank=True, on_delete=models.SET_NULL, related_name="reviews",)
experience = models.ForeignKey("experiences.Experience", null=True, blank=True, on_delete=models.CASCADE, related_name="reviews",)
payload = models.TextField()
rating = models.PositiveIntegerField()
def __str__(self) -> str:
return f"{self.user} / {self.rating}⭐"
# rooms -> admin.py
@admin.action(description="Set all prices to zero")
# action은 3개의 매개변수가 반드시 필요함
# model_admin은 액션을 호출한 클래스
# request 객체는 이 액션을 호출한 유저 정보를 갖고 있음
# rooms(queryset) 선택된 방에 대한 내용을 갖고 있음
def reset_prices(model_admin, request, rooms):
for room in rooms.all():
room.price = 0
room.save()
@admin.register(Room)
class RoomAdmin(admin.ModelAdmin):
actions = (reset_prices,)
# list_display에서 모델의 속성 뿐만 아니라 메소드도 적을 수 있음
list_display = (
"name",
"price",
"kind",
"total_amenities",
"rating",
"owner",
"created_at",
)
list_filter = (
"country",
"city",
"pet_friendly",
"kind",
"amenities",
"created_at",
)
# ^(startswoth) ->시작하는 단어로 검색하고 싶음
# =(exact)는 완전히 동일한 것으로 검색하고 싶음
# 아무것도 적지 않으면 contains
search_fields = ("name", "^price", "=owner__username",)
# rooms -> models.py
created_at = models.DateTimeField(auto_now_add=True) # auto_now_add는 필드의 값을 해당 object가 처음 생성되었을 때 시간으로 설정함 -> room이 만들어지면 django는 이 방이 만들어진 date를 이 부분에 넣음
updated_at = models.DateTimeField(auto_now=True) # auto_now는 저장될 때마다 해당 필드를 현재 date로 설정함
def rating(room):
count = room.reviews.count()
# reviews -> related_name --> review 모델에서 room을 가리키는 foreign key에 부여함
# rooms -> serializer.py
class RoomDetailSerializer(ModelSerializer):
# owner를 가져올 때 TinyUserSerializer를 사용하라고 알려줌, read_only -> 우리가 방을 생성할 때 serializer는 owner에 대한 정보를 요구하지 않음
owner = TinyUserSerializer(read_only=True)
amenities = AmenitySerializer(read_only=True, many=True)
category = CategorySerializer(read_only=True) # array가 아니고 숫자 하나면 many=True를 사용X
rating = serializers.SerializerMethodField() # potato의 값을 계산할 method를 만들라고함
is_owner = serializers.SerializerMethodField()
is_liked = serializers.SerializerMethodField()
photos = PhotoSerializer(many=True, read_only=True)
class Meta:
model = Room
fields = "__all__"
def get_rating(self, room): # 이름앞에 무조건 get_을 붙여야 함
print(self.context)
return room.rating()
def get_is_owner(self, room):
request = self.context['request']
return room.owner == request.user
def get_is_liked(self, room):
request = self.context['request']
return Wishlist.objects.filter(user=request.user, rooms__pk=room.pk).exists() # 유저가 여러 개의 wishlist를 가질 수 있기 때문에 filter 사용
# rooms -> views.py
def post(self, request):
# 사용자의 데이터로 serializer를 만들 때는 serializer는 사용자의 데이터가 amenity object가 원하는 데이터에 준수하는지 검증해야 함
serializer = serializers.AmenitySerializer(data=request.data)
if serializer.is_valid():
# True일 경우 serializer.save를 해서 ModelSerializer가 자동으로 amenity를 만들게 해야함
amenity = serializer.save()
# 새로 만들어진 것을 serialize한 다음 리턴해줌
return Response(serializers.AmenitySerializer(amenity).data,)
else:
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST,)
# partial은 부분 업데이트로 name 또는 description을 변경할 수 있음(둘 다X, 둘 중에 하나)
serializer = serializers.AmenitySerializer(amenity, data=request.data, partial=True,)
def get(self, request, pk):
try:
page = request.query_params.get("page", 1) # 페이지가 url에 있지 않다면 기본값으로 페이지 1을 요청함
page = int(page) # 페이지가 url에 있고 숫자로 되어 있다면 문자열을 실제 숫자 타입으로 바꿔야 함, 페이지가 그냥 문자라면 함수는 작동 안함
except ValueError:
page = 1 # url에 페이지가 없거나 유저가 잘못된 페이지를 보내는 상황이어도 페이지는 1이 됨
page_size = 3
start = (page - 1) * page_size
end = start + page_size
room = self.get_object(pk)
serializer = ReviewSerializer(room.reviews.all()[start:end], many=True,) # 처음부터 모든 리뷰를 업로드 후 자르는 방식이 아닌, 시작과 끝 지점을 갖고 가서 db에서 살펴보는 방식
return Response(serializer.data)
# Tip)
# import 할 때 django package에서 오는 것들을 우선시함
# 그 다음은 third party(이외의 것) package에서 import함 ex) rest_framework 등
# 다음은 같은 앱의 것들을 import 함, .로 시작하는 뜻은 같은 앱, 같은 폴더 안에 있다는 뜻
# 다음은 커스텀 앱에 관한 것들을 import함
# bookings = Booking.objects.filter(room=room, kind=Booking.BookingKindChoices.ROOM, check_in__gt=now,)
# 우리 서버 위치의 현지시각을 localtime을 이용해 구하고 check_in 날짜가 우리가 있는 곳의 현재 날짜보다 큰 booking을 찾고 있음
# users -> models.py
# models.py를 수정할 때마다 migratiob을 만들고 migrate 해야함
# 코드에 있는 모델 구조와 db 구조를 서로 동기화 하기 위해서임
class User(AbstractUser):
class GenderChoices(models.TextChoices):
MALE = ("male", "Male")
FEMALE = ("female", "Female")
class LanguageChoices(models.TextChoices):
KR = ("kr", "Korean")
EN = ("en", "English") # db 값은 max_length 값보다 작아야함
class CurrencyChoices(models.TextChoices):
WON = "won", "Korean Won"
USD = "usd", "Dollar"
first_name = models.CharField(max_length=150, editable=False)
last_name = models.CharField(max_length=150, editable=False)
avatar = models.URLField(blank=True,) # blank=True는 form에서 필드가 필수적이지 않게 해줌
name = models.CharField(max_length=150, default="")
is_host = models.BooleanField(default=False) # 모든 사용자는 is_host 값을 False로 받게됨
gender = models.CharField(max_length=10, choices=GenderChoices.choices,)
language = models.CharField(max_length=2, choices=LanguageChoices.choices,)
currency = models.CharField(max_length=5, choices=CurrencyChoices.choices,)
# users -> urls.py
path("token-login", obtain_auth_token), # username과 password를 보내면 token 반환
path("@<str:username>", views.PublicUser.as_view()), # 위 코드보다 먼저 쓰면 username을 me로 보기 때문에 에러 발생
# users -> views.py
permission_classes = [IsAuthenticated] # /me가 로그인한 user의 정보는 private 해야 하기 때문
class Users(APIView):
def post(self, request):
password = request.data.get('password')
if not password:
raise ParseError
serializer = serializers.PrivateUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
user.set_password(password)
user.save() # hash화된 비밀번호가 필요하기 때문
# user.password = password(X) - 절대 X(중요!)
serializer = serializers.PrivateUserSerializer(user)
return Response(serializer.data)
else:
return Response(serializer.errors)
------------------------------------------------
if user.check_password(old_password):
user.set_password(new_password) # new_password를 hash할 때만 작동
user.save()
# wishlists -> serializers.py
class Meta:
model = Wishlist
# 유저에게 위시리스트의 이름과 안에 있는 방을 보여줌(유저는 표시X) -> 위시리스트를 보고 있는 유저가 위시리스트의 소유자이기 때문
fields = ("pk", "name", "rooms",)
# wishlists -> views.py
class Wishlists(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
all_wishlists = Wishlist.objects.filter(user=request.user) # 동일한 유저가 갖고 있는 위시리스트만 찾기 위해 filter 사용
serializer = WishlistSerializer(all_wishlists, many=True, context={"request" : request},)
return Response(serializer.data)
def post(self, request):
serializer = WishlistSerializer(data=request.data)
if serializer.is_valid():
wishlist = serializer.save(user=request.user,) # 호출될 때 모델(위시리스트)을 받음
serializer = WishlistSerializer(wishlist)
return Response(serializer.data)
else:
return Response(serializer.errors)
# def put(self, request, pk, room_pk):
# wishlist = self.get_list(pk ,request.user)
# room = self.get_room(room_pk)
# if wishlist.rooms.filter(pk=room_pk).exists():
# wishlist.rooms.remove(room)
# else:
# wishlist.rooms.add(room)
# return Response(status=HTTP_200_OK)
# ManyToManyField(room의 list)로 들어가서 wishlist 내부의 roomlist로 접근해서 filter, db에서 room의 pk랑 일치하는 pk를 갖는 room이 있는지 확인
# room pk는 user가 url에 적은 room_pk로부터 확인 가능
# wishlist.rooms이 user가 등록, 삭제하려는 pk랑 일치하는 방을 갖고 있는지 확인하는 것
# 그냥 조건에 맞는 list만 반환하는 filter() 대신 exists()를 추가해 존재하는지 확인할 수 있음
# list에 room이 있으면, list에서 그 room을 지울 거고 그렇지 않으면 추가함
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import Amenity, Room
from users.serializers import TinyUserSerializer
from reviews.serializers import ReviewSerializer
from categories.serializers import CategorySerializer
from medias.serializers import PhotoSerializer
from wishlists.models import Wishlist
class AmenitySerializer(ModelSerializer):
class Meta:
model = Amenity
fields = ("name", "description",)
class RoomDetailSerializer(ModelSerializer):
# owner를 가져올 때 TinyUserSerializer를 사용하라고 알려줌, read_only -> 우리가 방을 생성할 때 serializer는 owner에 대한 정보를 요구하지 않음
owner = TinyUserSerializer(read_only=True)
amenities = AmenitySerializer(read_only=True, many=True)
category = CategorySerializer(read_only=True) # array가 아니고 숫자 하나면 many=True를 사용X
rating = serializers.SerializerMethodField() # potato의 값을 계산할 method를 만들라고함
is_owner = serializers.SerializerMethodField()
is_liked = serializers.SerializerMethodField()
photos = PhotoSerializer(many=True, read_only=True)
class Meta:
model = Room
fields = "__all__"
def get_rating(self, room): # 이름앞에 무조건 get_을 붙여야 함
print(self.context)
return room.rating()
def get_is_owner(self, room):
request = self.context['request']
return room.owner == request.user
def get_is_liked(self, room):
request = self.context['request']
return Wishlist.objects.filter(user=request.user, rooms__pk=room.pk).exists() # 유저가 여러 개의 wishlist를 가질 수 있기 때문에 filter 사용
class RoomListSerializer(ModelSerializer):
rating = serializers.SerializerMethodField()
is_owner = serializers.SerializerMethodField()
photos = PhotoSerializer(many=True, read_only=True)
class Meta:
model = Room
fields = ("pk", "name", "country", "city", "price", "rating", "is_owner","photos",)
def get_rating(self, room):
return room.rating()
def get_is_owner(self, room):
request = self.context['request']
return room.owner == request.user