DRF Serializer relations

허진수·2022년 7월 10일
0

Django

목록 보기
5/5

개요

모델 간의 관계를 나타낼 경우 사용한다.
ForeignKey, ManyToManyField, OneToOneField 등에 적용 할 수 있다.
ModelSerializer를 사용할 경우 기본적으로 PrimaryKeyRelatedField를 사용하여 자동으로 생성 해 준다.

예시로 사용할 Model

class Album(models.Model):
    album_name = models.CharField(max_length=100)
    artist = models.CharField(max_length=100)

class Track(models.Model):
    album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
    order = models.IntegerField()
    title = models.CharField(max_length=100)
    duration = models.IntegerField()

    class Meta:
        unique_together = ['album', 'order']
        ordering = ['order']

    def __str__(self):
        return '%d: %s' % (self.order, self.title)

종류

StringRelatedField

‘__str__’ 를 사용하여 표현된 관계를 나타낼 경우 사용

예시

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.StringRelatedField(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

예상 직렬화 결과

{
    'album_name': 'Things We Lost In The Fire',
    'artist': 'Low',
    'tracks': [
        '1: Sunflower',
        '2: Whitetail',
        '3: Dinosaur Act',
        ...
    ]
}

PrimaryKeyRelatedField

Primary key 로 관계된 필드를 표현

에시

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

예상 직렬화 결과

{
    'album_name': 'Undun',
    'artist': 'The Roots',
    'tracks': [
        89,
        90,
        91,
        ...
    ]
}

HyperlinkedRelatedField

Hyperlink로 관계된 필드를 표현

예시

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='track-detail'
    )

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

예상 직렬화 결과

{
    'album_name': 'Graceland',
    'artist': 'Paul Simon',
    'tracks': [
        'http://www.example.com/api/tracks/45/',
        'http://www.example.com/api/tracks/46/',
        'http://www.example.com/api/tracks/47/',
        ...
    ]
}

SlugRelatedField

primary key로 관계된 target의 다른 필드를 표현

예시

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.SlugRelatedField(
        many=True,
        read_only=True,
        slug_field='title'
     )

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

예상 직렬화 결과

{
    'album_name': 'Dear John',
    'artist': 'Loney Dear',
    'tracks': [
        'Airport Surroundings',
        'Everything Turns to You',
        'I Was Only Going Out',
        ...
    ]
}

HyperlinkedIdentityField

Identity relationship에 적용 가능
hyperlink로 관계 표현

예시

class AlbumSerializer(serializers.HyperlinkedModelSerializer):
    track_listing = serializers.HyperlinkedIdentityField(view_name='track-list')

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'track_listing']

예상 직렬화 결과

{
    'album_name': 'The Eraser',
    'artist': 'Thom Yorke',
    'track_listing': 'http://www.example.com/api/track_list/12/',
}

Nested relationships

중첩된 관계를 serializer를 사용하여 표현 가능

예시

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ['order', 'title', 'duration']

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

예상 직렬화 결과

>>> album = Album.objects.create(album_name="The Grey Album", artist='Danger Mouse')
>>> Track.objects.create(album=album, order=1, title='Public Service Announcement', duration=245)
<Track: Track object>
>>> Track.objects.create(album=album, order=2, title='What More Can I Say', duration=264)
<Track: Track object>
>>> Track.objects.create(album=album, order=3, title='Encore', duration=159)
<Track: Track object>
>>> serializer = AlbumSerializer(instance=album)
>>> serializer.data
{
    'album_name': 'The Grey Album',
    'artist': 'Danger Mouse',
    'tracks': [
        {'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
        {'order': 2, 'title': 'What More Can I Say', 'duration': 264},
        {'order': 3, 'title': 'Encore', 'duration': 159},
        ...
    ],
}

Writable nested serializers

기본적으로 nested serializers는 read-only이다.
쓰기를 하기 위해서는 create, update 등의 메쏘드를 작성해야 한다.

예시

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ['order', 'title', 'duration']

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

    def create(self, validated_data):
        tracks_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        for track_data in tracks_data:
            Track.objects.create(album=album, **track_data)
        return album

사용

>>> data = {
    'album_name': 'The Grey Album',
    'artist': 'Danger Mouse',
    'tracks': [
        {'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
        {'order': 2, 'title': 'What More Can I Say', 'duration': 264},
        {'order': 3, 'title': 'Encore', 'duration': 159},
    ],
}
>>> serializer = AlbumSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.save()
<Album: Album object>

Custom Serializer Field

Custom Relational Fields

위의 예시중 맞는 표현이 없을 경우 커스텀 할 수 있다.
커스텀을 위해서는 다음이 만족되어야 한다.
  • RelatedField 오버라이드

    • .to_representation(self, value) implement
      value를 통해 target objects를 받음
    • .to_internal_value(self, data)
      read-write 필드를 만들고 싶을 경우
    • .get_queryset(self) context기반 동적 queryset을 제공하고 싶을 경우
      class에서 queryset 명시할 필요 없음

예시

import time

class TrackListingField(serializers.RelatedField):
    def to_representation(self, value):
        duration = time.strftime('%M:%S', time.gmtime(value.duration))
        return 'Track %d: %s (%s)' % (value.order, value.name, duration)

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackListingField(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

예상 직렬화 결과

{
    'album_name': 'Sometimes I Wish We Were an Eagle',
    'artist': 'Bill Callahan',
    'tracks': [
        'Track 1: Jim Cain (04:39)',
        'Track 2: Eid Ma Clack Shaw (04:19)',
        'Track 3: The Wind and the Dove (04:34)',
        ...
    ]
}

Custom Hyperlinked Fields

하나가 아닌 여러개의 lookup field를 표현할 때 커스텀 가능하다
두개의 메쏘드를 오버라이드 해야한다.

get_url(self, obj, view_name, request, format)
	object instance와 url을 매핑할 경우 사용
	view_name 혹은 lookup_field가 설정되지 않으면 NoReverseMatch에러 발생
get_object(self, view_name, view_args, view_kwargs)
	write가능한 hyperlinkedfield를 커스텀 하고 싶을 경우
	urls를 해당하는 objects로 변환
	return value는 해당하는 object여야함 그렇지 않으면 ObjectDoesNotExist 예외 발생

예시

#다음의 url이 있을 경우
/api/<organization_slug>/customers/<customer_pk>/

from rest_framework import serializers
from rest_framework.reverse import reverse

class CustomerHyperlink(serializers.HyperlinkedRelatedField):
    # arguments로 전달할 필요 없음(class method)
    view_name = 'customer-detail'
    queryset = Customer.objects.all()

    def get_url(self, obj, view_name, request, format):
        url_kwargs = {
            'organization_slug': obj.organization.slug,
            'customer_pk': obj.pk
        }
        return reverse(view_name, kwargs=url_kwargs, request=request, format=format)

    def get_object(self, view_name, view_args, view_kwargs):
        lookup_kwargs = {
           'organization__slug': view_kwargs['organization_slug'],
           'pk': view_kwargs['customer_pk']
        }
        return self.get_queryset().get(**lookup_kwargs)
  • generic views에서 사용하기 위해서는 view에서 .get_object override필요

  • drf에서는 flat한 URL스타일을 추천함


추가

Dynamic Serializer Field

Serializer의 필드를 동적으로 바꿀 수 있다.

예시

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # superclass에 fields arguments전달 x
        fields = kwargs.pop('fields', None)

        # super class 초기화
        super().__init__(*args, **kwargs)

        if fields is not None:
            # fidles argument에 명시되지 않은 필드 제거
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

사용 예시

>>> class UserSerializer(DynamicFieldsModelSerializer):
>>>     class Meta:
>>>         model = User
>>>         fields = ['id', 'username', 'email']
>>>
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}

정리 및 생각

serializer에서 model간의 여러 관계를 표현 할 수 있다.

실제로 이를 잘 활용 할 경우 코드의 길이가 많이 짧아 질 것으로 생각된다.

related field의 경우 내부적으로 쿼리가 어떻게 되는지 잘 모른다.

select_related, prefetch_related와 성능 차이가 있는지 확인이 필요하다.

Dynamic Serializer Field는 유용할 것 같다.
profile
안녕하세요

0개의 댓글