Django Serializer DeepDive(작성중..)

Developer:Bird·2021년 6월 20일
0

Django

목록 보기
1/1

0.얼마나 깊이 팔것인지..?

Serializier를 자유롭게 커스텀마이징 하기 위한 정도로의 깊이를 알고 싶다. Product-level의 개발을 하는데 있어서, 수많은 Model을 가지고 있고, Dto로 활용하는데 있어서 다양한 방법으로 처리할 필요가 있고, 이를 위해 깊은 이해가 필요하다.

1.상속관계

먼저 ModelSerializer->Serializer->BaseSerializer->field 이며 데코레이터 패턴으로 구성되어있다.
실제로 어떻게 작동하는지 Top-Down으로 살펴보자.

2.ModelSerializer

 class ModelSerializer(Serializer):
 """
    A `ModelSerializer` is just a regular `Serializer`, except that:

    * A set of default fields are automatically populated.
    * A set of default validators are automatically populated.
    * Default `.create()` and `.update()` implementations are provided.

    The process of automatically determining a set of serializer fields
    based on the model fields is reasonably complex, but you almost certainly
    don't need to dig into the implementation.

    If the `ModelSerializer` class *doesn't* generate the set of fields that
    you need you should either declare the extra/differing fields explicitly on
    the serializer class, or simply use a `Serializer` class.
    """
    ...

Model Serializer를 정의 부분의 주석을 보면 다음과 같다. 이를 정리해보면 다음과 같다.
1. model의 필드에 근거해서 필드를 자동으로 채워준다.
2. 자동으로 Validator를 실행시켜준다.
3. create(), update()를 제공해준다.

2.1 def create(self, validated_data):

    def create(self, validated_data):
        """
        We have a bit of extra checking around this in order to provide
        descriptive messages when something goes wrong, but this method is
        essentially just:

            return ExampleModel.objects.create(**validated_data)

        If there are many to many fields present on the instance then they
        cannot be set until the model is instantiated, in which case the
        implementation is like so:

            example_relationship = validated_data.pop('example_relationship')
            instance = ExampleModel.objects.create(**validated_data)
            instance.example_relationship = example_relationship
            return instance

        The default implementation also does not handle nested relationships.
        If you want to support writable nested relationships you'll need
        to write an explicit `.create()` method.
        """
        raise_errors_on_nested_writes('create', self, validated_data)

        ModelClass = self.Meta.model

        # Remove many-to-many relationships from validated_data.
        # They are not valid arguments to the default `.create()` method,
        # as they require that the instance has already been saved.
        info = model_meta.get_field_info(ModelClass)
        many_to_many = {}
        for field_name, relation_info in info.relations.items():
            if relation_info.to_many and (field_name in validated_data):
                many_to_many[field_name] = validated_data.pop(field_name)

        try:
            instance = ModelClass.objects.create(**validated_data)
        except TypeError:
            tb = traceback.format_exc()
            msg = (
                'Got a `TypeError` when calling `%s.objects.create()`. '
                'This may be because you have a writable field on the '
                'serializer class that is not a valid argument to '
                '`%s.objects.create()`. You may need to make the field '
                'read-only, or override the %s.create() method to handle '
                'this correctly.\nOriginal exception was:\n %s' %
                (
                    ModelClass.__name__,
                    ModelClass.__name__,
                    self.__class__.__name__,
                    tb
                )
            )
            raise TypeError(msg)

        # Save many-to-many relationships after the instance is created.
        if many_to_many:
            for field_name, value in many_to_many.items():
                set_many(instance, field_name, value)

        return instance

이에 대해 분석해보면 다음과 같다.
argumnet로 validate_data(validation된 데이터)를 받게 되는데, 이에 관해서 다음의 과정을 거치게 된다.
1. Meta에 등록한 모델의 field정보를 가져오는데 (외래키인지,기본키인지와 같은 정보) Many to Many field의 경우는 제외시킨후 인스턴스를 생성하게 된다.
2. 생성하게 된 인스턴스에서 many_to_many field를 추가한 후에 return한다.
이때 중첩관계에 관해서는 지원하지 않는다.
다음과 같이 1번과 2번의 과정을 왜 거치는지 생각을 해보면 manytomany 관계를 추가하는 과정을 살펴보면 모델을 생성하고 인스턴스화를 시키고 나서 할 수 있기 때문이다. 왜 그런지 살펴보면, many to many에 해당하는 레코드를 생성하기 위해서는 참조하고 있는 record의 외래키를 알아야 한다. 하지만 objects.create()하기 전까지는 실제pk를 알 수 가없다. create()동작시 다음에 삽입할 pk number를 가져오게 된다. 따라서 2번의 과정을 거치게 된다. 또다른 관점으로는 객체지향언어와 관계형 테이블 사이에서 생기는 패러다임 차이라고 볼 수 있다. manytomany외래키는 연관관계의 주인에 해당하는 조인테이블에서 관리하고 있다. 따라서 생성되는 모델에서는 외래키를 가지고 있을 필요가 없다. 따라서 인스턴스를 생성하고 나서 추가하는게 맞다. (그러나 실무에서는 many to many field 사용대신 mapping table을 사용하기에 그렇게 중요하진 않다.)

2.2 def update(self, instance, validated_data):

    def update(self, instance, validated_data):
        raise_errors_on_nested_writes('update', self, validated_data)
        info = model_meta.get_field_info(instance)

        # Simply set each attribute on the instance, and then save it.
        # Note that unlike `.create()` we don't need to treat many-to-many
        # relationships as being a special case. During updates we already
        # have an instance pk for the relationships to be associated with.
        for attr, value in validated_data.items():
            if attr in info.relations and info.relations[attr].to_many:
                set_many(instance, attr, value)
            else:
                setattr(instance, attr, value)
        instance.save()

        return instance

업데이트의 경우에 중요한점은 instance에 관해서 instance에 관해서 사용자가 입력한 데이터를 기반으로 instance를 생성하여 그대로 save해버린다. 이럴경우 변경된 field에 관해서만 쿼리정보를 남기는게 아닌 필드수에 비례해서 날린다는것을 고려하자!또한 update의 경우 create과 다르게 many to many에 관해서 두번나눌 필요가 없다. 왜냐면 참조할 fk를 이미 가지고 있기 때문이다.

2.3 def get_fields(self)

다음 필드의 경우 개인적으로 Serializer가 기능하는 부분중 가장 중요한 부분이라고 생각한다~! 왜냐하면 이부분이 없다면 사실 serializer없이 Model만 사용하면 되기 때문이다. dto의 역할을 위해 Serializer를 사용한다면 바로 이 부분을 활용하기 위함이라고 생각된다. 코드를 열어보면 다음과 같다.

    def get_fields(self):
        """
        Return the dict of field names -> field instances that should be
        used for `self.fields` when instantiating the serializer.
        """
        if self.url_field_name is None:
            self.url_field_name = api_settings.URL_FIELD_NAME

        assert hasattr(self, 'Meta'), (
            'Class {serializer_class} missing "Meta" attribute'.format(
                serializer_class=self.__class__.__name__
            )
        )
        assert hasattr(self.Meta, 'model'), (
            'Class {serializer_class} missing "Meta.model" attribute'.format(
                serializer_class=self.__class__.__name__
            )
        )
        if model_meta.is_abstract_model(self.Meta.model):
            raise ValueError(
                'Cannot use ModelSerializer with Abstract Models.'
            )

        declared_fields = copy.deepcopy(self._declared_fields)
        model = getattr(self.Meta, 'model')
        depth = getattr(self.Meta, 'depth', 0)

        if depth is not None:
            assert depth >= 0, "'depth' may not be negative."
            assert depth <= 10, "'depth' may not be greater than 10."

        # Retrieve metadata about fields & relationships on the model class.
        info = model_meta.get_field_info(model)
        field_names = self.get_field_names(declared_fields, info)

        # Determine any extra field arguments and hidden fields that
        # should be included
        extra_kwargs = self.get_extra_kwargs()
        extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs(
            field_names, declared_fields, extra_kwargs
        )

        # Determine the fields that should be included on the serializer.
        fields = OrderedDict()

        for field_name in field_names:
            # If the field is explicitly declared on the class then use that.
            if field_name in declared_fields:
                fields[field_name] = declared_fields[field_name]
                continue

            # Determine the serializer field class and keyword arguments.
            field_class, field_kwargs = self.build_field(
                field_name, info, model, depth
            )

            # Include any kwargs defined in `Meta.extra_kwargs`
            extra_field_kwargs = extra_kwargs.get(field_name, {})
            field_kwargs = self.include_extra_kwargs(
                field_kwargs, extra_field_kwargs
            )

            # Create the serializer field.
            fields[field_name] = field_class(**field_kwargs)

        # Add in any hidden fields.
        fields.update(hidden_fields)

        return fields

    # Methods for determining the set of field names to include...

이를 요약하면 serializer가 보여주게 될 필드이름을 리턴하게 되는데 이는 Serializer에 공개,또는 히든으로 표시한 필드들과 모델이 가지고 있는 필드들에 대해서 필드의 정보를 가공하여 담아서 리턴하게 된다.

이중 중요한 코드라인은 다음과 같다.

1.  declared_fields = copy.deepcopy(self._declared_fields)
2.  info = model_meta.get_field_info(model)
3.  field_names = self.get_field_names(declared_fields, info)

1번의 경우 Serializer에 선언한 field를 가져오는 코드이고, 2번의 경우는 model이 가지고 있는 필드정보를 가지고온다.

3번의 경우는 다음과 같이 작동한다.

  1. Serializer의 Meta class의 fields = 'all'일 경우
    serializer에 선언한 필드+ model의 모든 필드를 리턴한다.

  2. exclude = [?,?]가 있을경우
    serializer에 선언한 필드 + model의 exclude=[?,?]를 제외하고 남은 필드를 리턴한다.

  3. exclude= None, fields = None 일경우.
    아무것도 리턴하지 않는다.

profile
끈임없이 발전하자.

0개의 댓글