ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django] Serializer HiddenField 활용
    Programming/Python 2023. 6. 13. 08:02

    A field class that does not take a value based on user input, but instead takes its value from a default value or callable.

    HiddenField 는 위의 설명과도 같이 클라이언트가 입력해서 넣은 값이 아닌 정해진 값 혹은 호출가능한 객체에서 입력을 대신한다.

    홈페이지 예제

    modified = serializers.HiddenField(default=timezone.now)
    

    위의 예제는 해당 요청을 받은 순간의 시간을 설정한다.

    하지만 해당하는 부분은 아래의 예제로 어느정도 조작이 가능하기에 방법의 다양성을 늘리는 측면이라고 생각하면 될 것 같다.

     

    modified = models.DatetimeField(auto_now=True)
    

     

    실질적으로 유효한 부분은 drf 홈페이지에서 validators 에 있는데

    owner = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
    

    drf 에서 제공하는 CurrentUserDefault 로 현재 요청한 유저를 가져온다.

     

    CurrentUserDefault

    class CurrentUserDefault:
        requires_context = True
    
        def __call__(self, serializer_field):
            return serializer_field.context['request'].user
    
        def __repr__(self):
            return '%s()' % self.__class__.__name__
    

    call 이 되는 순간을 보면 serializer_field 를 인자값으로 받는다.

    그리고 context 에서 접근해 request 에 있는 user를 반환하다.

     

    별로 어렵지는 않다. 하지만 현재 user에 대한 접근만 가능하다. hidden으로 받아야하는 것은 더 있을 수도 있다.

     

    나의 경우에는 이러했다.

    URL : repositories/3/folders 로 POST 요청을 보내고 데이터를 아래와 같이 보냈다

    {
    	"repository_id": 3,
    	"name": "오전에 해야할 것",
    	"performed": "2022-12-27 11:00:00"
    }
    

    그런데 여기에서 약간의 불편함이 있었다.

     

    그것은 나는 이미 URL을 통해서 repository_id 정의를 했는데도 다시 BODY에 repository_id 를 써야한다는 것이었다.

    repository_id 를 빼면 내가 원하는 방식으로 동작을 하지는 않았다.

     

    그렇기에 나는 암묵적으로 해당하는 부분이 동작하게 해야했다.

     

    그렇게 해서 떠오른 것이 CurrentUserDefault였고 해당하는 부분을 약간 내 방식으로 변경했다.

     

    일단 내 조건은 이랬다.

    1. URL 에 이미 정보가 있는 경우 해당하는 정보를 통해서 serializer가 받아야한다.
    2. Init 단계에서 어디에 접근할 지 정의를 해야하고 해당하는 return 값을 정의한다.
    class CurrentContext:
        requires_context = True # 1
    
        def __init__(self, key, coerce=int): # 2
            self.key = key
            self.coerce = coerce
    
        def __call__(self, serializers_field): # 3
    
            return self.coerce(
                serializers_field.context["request"].parser_context["kwargs"][self.key]
            )
    

    그렇게 해서 작성한 코드가 이렇다.

    1번을 보면 requires_context 라고 있는데 해당하는 부분을 True로 해주어야지 call 단계에서 serializers_field를 인자값으로 받을 수 있다. 안한 경우에는 받는 것이 불가하다.

     

    2번을 보면 key, coerce 을 받는데 key 의 경우에는 궁극적으로 parser_context의 kwargs 접근을 해야하니 해당 이름을 key 로 했고 coerce 의 경우에는 django-filter 에서 필터 중에서 coerce 를 통해서 타입을 강제하는 역할을 하는 코드를 보고 차용했다.

     

    3번을 보면 request 에 있는 parser_context 에 접근해서 init 단계에서 설정한 key 와 coerce 를 통해서 원하는 값을 리턴한다.

     

    실질적으로 쓰이는 코드는 아래와 같다.

    class FolderSerializer(ModelSerializer):
        repository_id = serializers.HiddenField(
            default=CurrentContext(key="repository_pk", coerce=str)
        )
    
        class Meta:
            model = Folder
            fields = ["repository_id", "id", "name", "performed"]
    

    일단 이렇게 함으로 통해서 parser_context 에 있는 kwargs 에 접근해 원하는 key 값을 가져오는 것이 가능해졌다.

     

    그리고 결국에는 요청하는 URL 을 통한 값을 전달받다보니 조금 더 명확한 느낌을 준다.

     

    개인적인 사견

    일단 나는 URL 을 아주 중요하게 생각한다.

    내가 무엇에 대해 접근을 하는 지 표현해주는 지표이기 때문이다.

     

    간혹 보면 URL에 정보가 너무 빈약하거나 query_parameter 로 모든 것을 대체하는 경우가 있는데 내 기준에서는 그것이 불편하다.

    예시로 방금 전에 나온 URL 과 그것보다 조금 더 나아간 부분으로 들겠다.

    1. [GET] repositories/3/folders ⇒ path varialbe
    2. [GET] folders?repository_pk=3 ⇒ query parameter

    해당하는 부분을 보면 이해하는 데 그렇게 큰 문제는 없을 것이다.

    두 URL 모두 repository 를 외래키로 두는 folder 가 존재한다는 것은 알 수가 있다.

    1. [GET] repositories/3/sheets/1/revisions/1/descriptions
    2. [GET] descriptions?sheet_pk=1&revisions_pk=1&repositories_pk=3

    해당하는 부분을 보면 이제부터 이해하기가 약간 어려워진다.

    만약 두번째 URL을 보고 resource의 내포관계를 설명하려고 하면 벌써부터 막막하다.

     

    하지만 첫번째 URL을 보면 의외로 설명하기가 쉽다.

    왜냐하면 왼쪽부터 오른쪽으로 순차적으로 읽으면 된다.

    하지만 query_parameter 의 경우에는 순서가 상관이 없고 python 스럽게 던더를 통해서 해당하는 부분을 설명하면 괜찮을 수도 있지만 그것은 올바른 방법이 아닐 가능성이 크다.

    가장 중요하겐 url이 길어지면 길어질 수록 가독성은 떨어진다. 그런데 던더를 쓰면 가독성이 너무 안좋아진다.

     

    그래서 적절하게 사용하는 것이 필요하다.

     

    나같은 경우에는 기본키는 path variable 로 설정을 하고 기본키를 제외한 속성 값을 띄는 얘들을 query_parameter 로 사용한다.

     

    예시로 들면

    [GET] repositories/3/sheets/1/revisions?is_published=True(true, 1 ….)

    해당하는 것 처럼 말이다.

     

    설명을 하면 저장소 3번에 있는 악보 1번의 개정판 중에서 출판이 된 얘들만 가져온다.

    그리고 POST method 를 보낼 때도 내포관계를 확실히 알 수 있으니 url 을 통해서 sheet 의 pk를 가지고 바로 생성하는 것이 가능하다.

Designed by Tistory.