Study/django

[udemy] React, Typescirpt, Django, Channels and DRF. Building a live chat application - 학습 정리

bluebamus 2025. 4. 7.

강의 정보 : 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의 기본

출처 : https://testdriven.io/blog/django-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

 

 

댓글