[udemy] React, Typescirpt, Django, Channels and DRF. Building a live chat application - 학습 정리
강의 정보 : https://www.udemy.com/course/full-stack-react-django-drf-channels-project-djchat/
- react 항목은 제외하고 rest api와 channel 구현 항목만 정리함
1. SerializerMethodField()란?
- SerializerMethodField()는 Django REST framework(DRF)의 serializers 모듈에서 제공하는 특수한 필드로, 직렬화(Serialization)할 데이터를 동적으로 생성할 때 사용된다.
- 이 필드는 모델 필드와 직접적으로 매핑되지 않으며, 사용자가 정의한 메서드를 호출하여 값을 생성한다.
- 즉, 데이터베이스에 존재하지 않는 값이지만, 직렬화 과정에서 계산되거나 조작된 값을 포함하고 싶을 때 유용하다.
1.1. SerializerMethodField()의 사용법
- serializers.SerializerMethodField()를 정의한다.
- get_<필드이름> 형태의 메서드를 구현하여 원하는 값을 반환한다.
class ServerSerializer(serializers.ModelSerializer):
num_members = serializers.SerializerMethodField() # 동적 필드 추가
class Meta:
model = Server
fields = "__all__"
def get_num_members(self, obj):
# num_members 필드 값이 객체(obj)에 존재하면 반환, 없으면 None 반환
if hasattr(obj, "num_members"):
return obj.num_members
return None
1.2. 동작 방식
- num_members = serializers.SerializerMethodField()을 정의하면, DRF는 get_num_members()라는 이름의 메서드를 자동으로 찾아서 실행한다.
- obj는 현재 직렬화되고 있는 모델 인스턴스를 나타낸다.
- get_num_members(self, obj)가 실행되어, obj.num_members 값을 반환하거나 존재하지 않으면 None을 반환한다.
- 이렇게 반환된 값이 직렬화된 JSON 응답에 포함된다.
2. channels의 기본
3. consumer
공식 매뉴얼 : https://channels.readthedocs.io/en/stable/topics/consumers.html
4. channel layers
공식 매뉴얼 : https://channels.readthedocs.io/en/latest/topics/channel_layers.html
- 채널 레이어는 순수하게 비동기적인 인터페이스(송신과 수신 둘 다)를 갖고 있다.
- 동기 코드에서 호출하려면 이를 변환기로 래핑해야 한다.
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.send)("channel_name", {...})
- 채널 레이어를 통한 데이터 전송
await self.channel_layer.group_send(
room.group_name,
{
"type": "chat.message",
"room_id": room_id,
"username": self.scope["user"].username,
"message": message,
}
)
async def chat_message(self, event):
"""
Called when someone has messaged our chat.
"""
# Send a message down to the client
await self.send_json(
{
"msg_type": settings.MSG_TYPE_MESSAGE,
"room": event["room_id"],
"username": event["username"],
"message": event["message"],
},
)
- 그룹 추가 및 제거
# This example uses WebSocket consumer, which is synchronous, and so
# needs the async channel layer functions to be converted.
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
def connect(self):
async_to_sync(self.channel_layer.group_add)("chat", self.channel_name)
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)("chat", self.channel_name)
5. Simple JWT와 MRO(Method Resolution Order) 상속
공식 매뉴얼 : https://django-rest-framework-simplejwt.readthedocs.io/en/latest/rest_framework_simplejwt.html#
- rest framework simplejwt package는, access token, refresh token을 관리하기 위한 view 클래스와 serializer 클래스를 제공한다. 이에 원하는 항목에 대해 커스텀 코드를 추가하는 수준 만으로 쉽게 구현할 수 있다.
- 강의에서는 finalize_response를 오버라이딩하여 기존의 토큰 이름, life time, samesite 설정을 setting에 설정 된 값으로 업데이트하는 과정을 추가한다.
class JWTSetCookieMixin:
def finalize_response(self, request, response, *args, **kwargs):
if response.data.get("refresh"):
response.set_cookie(
settings.SIMPLE_JWT["REFRESH_TOKEN_NAME"],
response.data["refresh"],
max_age=settings.SIMPLE_JWT["REFRESH_TOKEN_LIFETIME"],
httponly=True,
samesite=settings.SIMPLE_JWT["JWT_COOKIE_SAMESITE"],
)
if response.data.get("access"):
response.set_cookie(
settings.SIMPLE_JWT["ACCESS_TOKEN_NAME"],
response.data["access"],
max_age=settings.SIMPLE_JWT["ACCESS_TOKEN_LIFETIME"],
httponly=True,
samesite=settings.SIMPLE_JWT["JWT_COOKIE_SAMESITE"],
)
del response.data["access"]
return super().finalize_response(request, response, *args, **kwargs)
class JWTCookieTokenObtainPairView(JWTSetCookieMixin, TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
class JWTCookieTokenRefreshView(JWTSetCookieMixin, TokenRefreshView):
serializer_class = JWTCookieTokenRefreshSerializer
- 이 코드에서 중요한 항목은 JWTCookieTokenObtainPairView의 상속 클래스이다. JWTSetCookieMixin는 기본 클래스인데 APIView에 정의된 finalize_response를 재정의하고 super().finalize_response()를 호출한다.
- Mixin이 super().finalize_response()를 호출할 수 있는 이유
- 먼저, 현재 코드에서 JWTSetCookieMixin은 단독으로 사용될 수 없는 Mixin이다.
- 즉, 단독으로 인스턴스화되지 않으며, 반드시 다른 클래스를 상속하는 클래스에 포함되어야 한다.
- 아래의 코드에서 MRO가 어떻게 형성되는지 보자면,
class JWTSetCookieMixin:
def finalize_response(self, request, response, *args, **kwargs):
print("JWTSetCookieMixin.finalize_response 실행")
return super().finalize_response(request, response, *args, **kwargs)
class TokenObtainPairView:
def finalize_response(self, request, response, *args, **kwargs):
print("TokenObtainPairView.finalize_response 실행")
return "Response finalized"
class JWTCookieTokenObtainPairView(JWTSetCookieMixin, TokenObtainPairView):
pass
print(JWTCookieTokenObtainPairView.mro())
- MRO 출력
[<class '__main__.JWTCookieTokenObtainPairView'>,
<class '__main__.JWTSetCookieMixin'>,
<class '__main__.TokenObtainPairView'>,
<class 'object'>]
- super().finalize_response()가 동작하는 원리
- JWTCookieTokenObtainPairView 인스턴스를 생성하고 finalize_response()를 호출하면,
- MRO에서 JWTSetCookieMixin의 finalize_response()가 먼저 실행된다.
- Mixin이 super().finalize_response()를 호출
- super()는 MRO에 따라 다음 클래스인 TokenObtainPairView를 탐색한다.
- TokenObtainPairView에서 finalize_response()가 정의되어 있으므로, 이 메서드가 실행된다.
- 최종적으로 TokenObtainPairView.finalize_response()가 실행되어 요청을 처리한다.
- Mixin이 부모 클래스 없이도 super()를 호출할 수 있는 이유
- Mixin 자체는 부모가 없지만, MRO에서 자신 뒤에 오는 클래스를 super()로 호출할 수 있다.
- 즉, JWTSetCookieMixin은 TokenObtainPairView가 자신보다 뒤에 존재할 것을 가정하고 super()를 호출한다.
6. 학습 종료
- channels와 관련한 코드는 group_add, group_send, group_discard를 사용하지만, 1:1 메시지 전송을 안다면 크게 차이가 나지 않는다.
- react 코드에 문제가 많다. 쿠키의 저장이 되지 않는데 원인을 알 수 없다. django의 미들웨어에서 쿠키를 확인하면 전송은 제대로 하지만 이후 요청에 쿠키가 없고 react의 로그인 항목에서 console.log() 및 쿠키를 출력해보면 쿠키 정보가 없다.
- docs에 접속해 api 테스트를 하면 login에 쿠키가 전송은 된다. 하지만 쿠키의 전송은 알 수 없다.
- 더 이상 학습이 무의미 하여 종료함 대부분의 학습이 react 구현이다.
- reference :
https://channels.readthedocs.io/en/latest/
https://testdriven.io/blog/django-channels/
Introduction to Django Channels
This tutorial shows how to use Django Channels to create a real-time application.
testdriven.io
댓글