사이드 프로젝트를 진행하면서 겪었던 나의 DRF 삽질 기록 🌟
viewsets.ViewSet
은 Pagination이 자동적으로 적용되지 않는다.
왜냐하면 공식문서에서 말하고 있는 것과 같이 pagination은 GenericAPIVIew에서만 자동적용되는데, 그냥 ViewSet
은 APIView
를 상속했기 때문이다.
처음에는 ViewSet
이라는 이름만 있으면 다 바로 적용되는줄 알았는데 그걸 모르고 엄청 삽질했다.
만약 settings.py
에서 REST_FRAMEWORK
에 적용하여 grobally하게 사용(모든 리스트 뷰에서 해당 페이지네이션 사용)하고 싶다면 GenericAPIView
를 상속한 뷰셋을 써야하고 나는 ModelViewSet
을 사용하기로 했다.
ModelViewSet
은 CRUD를 모두 해결해주기 때문에 맨 처음에 선언한 serialzier_class를 모든 메소드에서 사용하게 된다.
하지만 나의 경우에는 리스트에서 보여지는 결과(GET
)와 객체를 하나 생성(POST
)할때 필요한 값들이 달랐다. 그런 경우에는 메소드에 해당하는 함수를 오버라이드 해주면 된다.
현재 POST
에 대한 serializer만 바꾸고 싶기 때문에 create
함수를 오버라이드하고 serializer_class
를 내가 원하는 serializer로 선언해준다.
serialziers.py
에서 로그인한 유저 확인하기좋아요, 팔로잉 기능을 만들었을때 is_like
, is_follow
를 serializer의 필드에 추가해서 로그인한 사용자에 따라서 true
와 false
값을 넣어야했다.
그렇게 하기 위해서는 view에서 serializer로 데이터를 넘길때 request를 한 유저(로그인한 유저)에 대한 정보도 같이 넘겨서 그 유저에 관한 좋아요 정보, 팔로잉 정보를 알아내야했다.
검색을 해보니 self.context['request'].user
로 가져올 수 있다는데 context가 없어서 계속 오류가 발생했다. 이것 때문에 구글을 계속 뒤져본 결과.....
>> stackoverflow
get_serialzier
함수를 보면 serializer에 context를 추가하는 것을 알 수 있다. 그래서 serializer_class = self.get_serialzier(instance)
를 generic view안에 추가해주니 로그인한 유저의 정보를 가져올 수 있었다.
이번에 자동 로그인 기능을 만들어보고 싶어서 cookie를 통해 토큰을 넘겨주고 싶었다. 쿠키를 만드는 것은 response
에 .set_cookie
만 추가해주면 되었기 때문에 어렵지는 않았다.
하지만 문제는 DRF의 TokenAuthentication은 헤더에서 토큰의 정보를 가져온다. 그렇기 때문에 토큰을 header가 아닌 cookie에 넘기면 토큰을 찾지 못한다. 그렇게되면 내가 설정했던 permission들도 말짱 도루묵...
다음과 같이 TokenAuthentication을 상속한 Authentication을 새로 만들어서 cookie에서 토큰을 찾도록하니 문제가 해결 되었다.
>> stackoverflow
그리고 XSS 공격에 대비하여 .set_cookie
에서 httponly
를 true
로 설정해주었다.
**UPDATE
토큰을 쿠키로 보냈을때 포스트맨으로 테스트를 해보면 쿠키가 잘 들어가는 것이 확인 되지만 프론트와 크롬으로 맞춰보면 크롬에서 쿠키를 받아들이지 않는 문제가 발생했다.
이것은 크롬의 변경된 보안 정책 때문인 듯 하다. samesite=Lax가 default로 적용되며 samesite=None을 사용하려면 secure=True를 반드시 함께 적용해주어야한다.
우리는 아직 도메인이 없기 때문에 samesite=None을 설정해주어야하는데 secure=True로 하려면 https로 통신을 해야한다.... 결국 해결하지 못하고 다시 로컬 스토리지 방식으로 돌아왔다.
작성자가 아니면 수정, 삭제를 할 수 없도록 permission을 설정해주어야 하는 상황이었다. 이런 상황에 딱맞는 permission이 없으므로 BasePermission
을 오버라이드해서 새로 만들어주어야 했다.
BasePermission
에는 has_permission
메소드와 has_object_permission
이 있는데 디테일 페이지 수정, 삭제의 권한을 부여하는 것이기 때문에 permission을 설정하면 has_object_permission
이 실행되어야하는데 has_permission
이 적용되고 있었다.
공식문서를 찾아보니 object level의 permission은 .get_object()
함수를 호출할때 실행된다고 한다.
>> Django REST Framework Docs
그래서 다음과 같이 get_object()
메소드를 선언해주었다.
아, 그리고 여기서 사용된 get_object_or_404
은 pk에 해당하는 object가 없을때 status code가 404인 response를 리턴해서 해당 상황에 대한 에러 핸들링이 따로 필요 없다. 나도 이번에 찾아보다가 알게 되었다. 👍🏻
setUp()을 통해 만들어준 object들의 아이디는 확신할 수 없다. setUp 을 한 후 id로 객체를 지정해주지 말 것.
>> stackoverflow