rooms/views.py
아래 73줄을 줄이도록 해볼게요.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.pagination import PageNumberPagination
from .models import Room
from .serializers import RoomSerializer
class OwnPagination(PageNumberPagination):
page_size = 3
class RoomsView(APIView): # collections로 던져줌
def get(self, request):
paginator = OwnPagination()
rooms = Room.objects.all()
results = paginator.paginate_queryset(rooms, request)
serializer = RoomSerializer(results, many=True, context={'request':request}) # context 키워드 인자는 별 100만개 중요함 serializer 인스턴스에게 정보를 넘겨줌
return paginator.get_paginated_response(serializer.data)
def post(self, request):
if not request.user.is_authenticated: # 분기1: 로그인 여부 확인
return Response(status=status.HTTP_401_UNAUTHORIZED)
serializer = RoomSerializer(data=request.data)
if serializer.is_valid(): # 분기2 : 유효성 검사 body로 받은 값들과 serializer에서 정의된 필드 형식과 변수명이 맞는지 확인
room = serializer.save(user=request.user) # 여기서 받은 룸은 create 메소드에서 반환한 인스턴스를 말함
room_serializer = RoomSerializer(room).data # 알맹이에 해당함. HTTP 메서드에서 바디로 보낸 값들과 기타등등
return Response(data=room_serializer, status=status.HTTP_200_OK)
else:
return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST) # data키워드 인자는 오류도 받아서 던져주기도합니다.
class RoomView(APIView): # single로 처리
def get_room(self, pk): # DB에 존재하면 긁어 오고 없으면 None반환, 일명 helper function
try:
room = Room.objects.get(pk=pk)
return room
except Room.DoesNotExist:
return None
def get(self, request, pk):
room = self.get_room(pk)
if room is not None:# room 객체가 정상적으로 DB에 있을 경우
serializer = RoomSerializer(room).data
return Response(serializer)
else: # Room 객체가 None에 해당할 경우
return Response(status=status.HTTP_404_NOT_FOUND)
def put(self, request, pk):
room = self.get_room(pk) # get_room()메서드 정의시 첫번째 인자로 self로 받아서 인스턴스 속성으로 사용 가능
if room is not None:
if room.user != request.user: # url parameter로 받아와 db에서 긁어온게 좌항 로그인한 유저의 정보가 우항
return Response(status=status.HTTP_403_FORBIDEN)
serializer = RoomSerializer(room, data=request.data, partial=True) # partial 사용해서 기본적으로 required fields를 모두 넣지 않아도 오류를 발생 시키지 않게함
if serializer.is_valid(): # db에 가져온 객체 정보와 클라이언트로부터 가져온 정보를 serializer와 비교했을때 유효한가?
room = serializer.save() # update() 메서드를 호출함. 객체 생성후 db에 저장함
return Response(RoomSerializer(room).data) # statusz 키워드를 적어주지 않아도 기본값은 200이라서 생략 가능
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_200_OK)
else: # pk로 뒤져본 room이 디비에 없어요.
return Response(status=status.HTTP_404_NOT_FOUND)
def delete(self, request, pk): # 3번째 인자는 urls.py에서 url 파라미터로 넘겨 받은 변수
room = self.get_room(pk) # 헬퍼 함수를 통해서 room이 db에 있는지 혹은 None인지 반환
if room is not None: # 객체가 존재하고
if room.user != request.user: # 로그인한 유저가 객체의 user가 아니면(만든사람)
return Response(status=status.HTTP_403_FORBIDDEN) # 403 오류를 뱉어내고
room.delete() # room.user == request.user 동일하면 delete()메소드를 사용해서 db에서 지워버림
return Response(status=status.HTTP_200_OK) # 정상 삭제되었다는 200 코드를 반환함
else:
return Response(status=status.HTTP_404_NOT_FOUND) # pk를 이용해서 조회했지만 없는 경우 404오류를 Response클래스에 담아서 반환함.
10줄도 안되는 것 같은데요. 클래스 멤버변수 2개, 메서드1개(7줄), 임포트 2개
위 70여줄이 이렇게 짧아진다는 점에서 얼마나 함축적이고 파워풀한지 알수 있습니다.
여기의 모든 중심은 바로 ModelViewSet이 있습니다.
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet # 추가
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.pagination import PageNumberPagination
from rest_framework import permissions # 인증/인가시 Viewset에서 사용
from .models import Room
from .serializers import RoomSerializer
class RoomViewSet(ModelViewSet):
queryset = Room.objects.all()
serializer_class = RoomSerializer
def get_permissions(self):
if self.action == "list" or self.action == "retrieve":
permission_classes = [permissions.AllowAny] # 어느 누구든 접근 가능
elif self.action == "create":
permission_classes = [permissions.IsAuthenticated] #로그인 유저만 가능
else:
permission_classes = [IsOwner] # 이외 삭제,수정은 해당 컨테츠의 처음 만든 유저만 가능
ViewSet 사용시 urls.py에서 사용해야하는 녀석이 바로 라우터에요
from rest_framework.routers import DefaultRouter
from . import views
app_name = "rooms"
router = DefaultRouter()
router.register("", views.RoomViewSet)
urlpatterns = router.urls
내용 :
rooms/serializers.py
의 RoomSerializer
Class에서 UserSerializer
클래스의 키워드 인자를 read_only=True
지정하고
룸시리얼라이저의 내부 메서드로 create를 오버라이딩하여 작성함. self.context.get('request') 부분을 request 변수로 받아서 룸 인스턴스 생성시 두번째 키워드 인자로 user에 할당하여 줌.
참고로 request객체가 넘어가는건 이미 작성된 get_serializer_context
덕분임
from rest_framework import serializers
from users.serializers import UserSerializer
from rooms.models import Room
class RoomSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True) # 이걸 적어줘야 HTML form양식이 browseable API에 나타나지 않음
is_fav = serializers.SerializerMethodField() # Serializer클래스의 메서드를 마치 필드와 같이 나타냄.
class Meta:
...
...
# 생략
def get_is_fav(self, obj): # 여기서 objs는 인스턴스들을 말하고 있
user = request.user # 디비의 유저 인스턴스가 request에 박혀있음
if user.is_authenticated: # 해당 유저 객체가 로그인 되었는지 확인함
return obj in user.favs.all() # True/ False 반환
return False
def create(self, validated_data):
request = self.context.get("request") # 이미 만들어진 get_serializer_context를 통해서 self.request값이 self인자를 통해서 전달되고 있음.
room = Room.objects.create(**validated_data, user=request.user)
return room
===============================================================
RoomViewSet IsOwner
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.pagination import PageNumberPagination
from rest_framework import permissions
from .models import Room
from .serializers import RoomSerializer
from .permissions import IsOwner
class RoomViewSet(ModelViewSet):
queryset = Room.objects.all()
serializer_class = RoomSerializer
def get_permissions(self):
if self.action == "list" or self.action == "retrieve":
permission_classes = [permissions.AllowAny] # 누구나 접근 가능
elif self.action == "create":
permission_classes = [permissions.IsAuthenticated] # 로그인 한 누구나
else:
permission_classes = [IsOwner] # 해당 데이터를 생성한 주인만!
return [permission() for permission in permission_classes] # 추가
Including/search in ViewSet
지금까지 기존에 작성했던 views.py에요.
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.pagination import PageNumberPagination
from rest_framework import permissions
from .models import Room
from .serializers import RoomSerializer
from .permissions import IsOwner
class RoomViewSet(ModelViewSet):
queryset = Room.objects.all()
serializer_class = RoomSerializer
def get_permissions(self):
if self.action == "list" or self.action == "retrieve":
permission_classes = [permissions.AllowAny]
elif self.action == "create":
permission_classes = [permissions.IsAuthenticated]
else:
permission_classes = [IsOwner]
return [permission() for permission in permission_classes]
@api_view(["GET"])
def room_search(request):
max_price = request.GET.get("max_price", None)
min_price = request.GET.get("min_price", None)
beds = request.GET.get("beds", None)
bedrooms = request.GET.get("bedrooms", None)
bathrooms = request.GET.get("bathrooms", None)
lat = request.GET.get("lat", None)
lng = request.GET.get("lng", None)
filter_kwargs = {}
if max_price is not None:
filter_kwargs["price__lte"] = max_price
if min_price is not None:
filter_kwargs["price__gte"] = min_price
if beds is not None:
filter_kwargs["beds__gte"] = beds
if bedrooms is not None:
filter_kwargs["bedrooms__gte"] = bedrooms
if bathrooms is not None:
filter_kwargs["bathrooms__gte"] = bathrooms
paginator = OwnPagination()
if lat is not None and lng is not None:
filter_kwargs["lat__gte"] = float(lat) - 0.005
filter_kwargs["lat__lte"] = float(lat) + 0.005
filter_kwargs["lng__gte"] = float(lng) - 0.005
filter_kwargs["lng__lte"] = float(lng) + 0.005
try:
rooms = Room.objects.filter(**filter_kwargs)
except ValueError:
rooms = Room.objects.all()
results = paginator.paginate_queryset(rooms, request)
serializer = RoomSerializer(results, many=True)
return paginator.get_paginated_response(serializer.data)
rooms/urls.py
url에 search를 하드코딩해서 작성하여 맵핑한 건 전혀 없이 아래 사진처럼 엔드포인트를 구현할 거에요.
from rest_framework.routers import DefaultRouter
from rooms import views
app_name = "rooms"
router = DefaultRouter()
router.register('',views.RoomViewSet)
urlpatterns = router.urls
rooms/views.py
딱 2줄만 교체 해주면 끝이에요.
메소드 이름이 결국 URL에 해당되기에 정말 직관적이게 표현되는거조.
action 은 첫번째 인자로 detail
값을 받습니다. Boolean 으로서 True 일 경우, pk 값을 지정해줘야하는 경우에 사용하고 False일 경우, 목록 단위로 적용하게 됩니다.
...
...
...
class RoomViewSet(ModelViewSet):
queryset = Room.objects.all()
serializer_class = RoomSerializer
@api_view(['GET'])
def room_search(request):
...
...
...
@action(detail=False)
def search(self, request):
...
...
...