Model 간의 관계를 Serializer를 통해 표현하려면 어떻게 해야 할까? 한 번 알아보자.
데이터 간의 관계는 OneToOne, ForeignKey, ManyToMany 등 다양하다. 이를 Serializer에서 표현하려면 어떻게 할까?
NOTE!
DRF에서는 쿼리 최적화까진 해주지 않는다. 이는 프로그래머의 몫이므로 쿼리 최적화를 하자.
API Reference
앨범과 트랙 모델을 통해 자세히 살펴보자
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__ 메서그를 통해 string으로 표현할 수 있다.
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
PK를 관계의 대상을 나타내는 데 사용할 수 있다.
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,
...
]
}
기본적으로 이 필드는 read-write 이지만, read_only 플래그를 사용할 수 있다.
HyperlinkedRelatedField
하이퍼링크된 관련 필드로 대상을 나타낼 수 있다.
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/',
...
]
}
Nested relationships
사실 이거 때문에 이 포스팅을 쓰게 되었다. 중첩 관계를 표현할 때 유용한다. 필드가 to-many 관계를 나타내는 데 사용되는 경우 serializer 필드에 many=True 플래그를 추가해야 한다.
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
중첩 관계는 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 relational fields
필요한 표현에 맞는 관계형 스타일이 없는 경우 구현할 수 있다. RelatedField를 재정의하고 to_representation() 메서드를 구현해야 한다. 이 메서드는 필드의 대상을 값 인수로 사용하며 대상을 직렬화하는 데 사용해야 하는 표현을 반환해야 한다. 값 인수는 일반적으로 모형 인스턴스가 된다. 읽기-쓰기 관계형 필드를 구현하려면 .to_internal_value() 메서드도 구현해야 한다. 콘텍스트를 기반으로 동적 쿼리 집합을 제공하려면 클래스에서 .queryset을 지정하거나 필드를 초기화할 때 대신 .get_queryset(self)을 재지정할 수도 있다
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)',
...
]
}
Reverse relations
역방향 관계는 ModelSerializer 및 HyperlinkedModelSerializer 클래스에 자동으로 포함되지 않는. 역방향 관계를 포함하려면 필드 목록에 역방향 관계를 명시적으로 추가해야 한다
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['tracks', ...]
일반적으로 필드 이름으로 사용할 수 있는 관계에 대한 적절한 related_name 인수를 설정해야 한다.
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
...
만약 이름을 설정하지 않은 경우 인수에서 자동으로 생성된 이름을 사용해야 한다.
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['track_set', ...]
'Django' 카테고리의 다른 글
Django Filterset 활용하기 (0) | 2023.03.13 |
---|---|
Django staticfile 관리하기 (0) | 2023.03.01 |
[DRF] create vs perform_create (0) | 2023.02.24 |
[DRF] APIView vs ViewSet (0) | 2023.02.20 |
Django 구조 잡기 (0) | 2023.02.18 |