[drf]airbnb-api -19 RoomViewSet

Hyeseong·2021년 4월 1일
0
post-thumbnail

리팩토링

리팩토링 전

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]						# 이외 삭제,수정은 해당 컨테츠의 처음 만든 유저만 가능

Router

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.pyRoomSerializer 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

  • 내용 :
    • BasePermission클래스를 상속 받아 IsOwner클래스를 커스터마이징 하여 작성함.
      • has_object_permission메서드를 작성하고 내부 인자로 self, request, view, room(obj)를 위치시킴
    • RoomViewSet클래스 안에 위에서 커스터마이징한 클래스를 장착하여 작성함
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

  • 일시 : 2021.03.14.17:41
  • 내용 :
    • 기존 search API를 RoomViewSet API와 통합함
      • action decorator 사용(detail=False),
      • 커스터마이징한 페이지네이션 클래스는 버리고 기존 제공하는 페이지내에션 기능 사용

@api_view 데코레이터 사용 시

지금까지 기존에 작성했던 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)

@action 데코레이터로 ModelViewSet API와 Endpoint 공유

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):
		...
		...
		...
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글