DRF: Полный пример ListSerializer как Admin-Inline в Django?
Я пытаюсь разобраться со списком DRF ListSerializer в сочетании с foreignkey-моделью. Цель - получить что-то похожее на Django's admin-inline Formula. К сожалению, я не могу найти полный пример в документации или SO примеры: две модели, два сериализатора и viewset.
Предположим, что у нас есть модель, немного похожая на модель в примере SO DRF ListSerializer и ListField :
class Musician(models.Model):
name = models.CharField(max_length=50)
comment = models.TextField(blank=True)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
Поэтому я пытаюсь поместить их в сериализаторы следующим образом:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = 'artist', 'title'
model = Album
class MusicianSerializer(serializers.ModelSerializer):
class Meta:
fields = 'name', 'comment', 'albums'
model = Musician
albums = serializers.ListSerializer(child=AlbumSerializer(), source='album_set')
Затем я хочу построить на нем ModelViewSet, чтобы я мог работать с ним, как в Django-Admin-Inline. Но это не работает просто так:
class MusicianViewSet(viewsets.ModelViewSet):
queryset = Musician.objects.all()
serializer_class = MusicianSerializer
Но это трудно проверить, HTML front-end действительно работает, и в тестах я получил (в немного другой ситуации) сообщение об ошибке, что-то вроде:
{'album_set': [ErrorDetail(string='This field is required.', code='required')]}
чего я не смог понять, даже после многочасового копания в исходном коде. Так чего же мне не хватает? А в идеале, каким должен быть полноценный рабочий пример с POST, PUT и DESTROY, создающий модификацию или удаление на дочерних объектах модели?
[Edit: I'm using Django==4.0.1 and django-extensions==3.1.5]
Вы можете использовать related_name в своей модели
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE, related_name='albums')
name = models.CharField(max_length=100)
затем вызовите его в вашем сериализаторе
class MusicianSerializer(serializers.ModelSerializer):
albums = AlbumSerializer(many=True, read_only=True)
class Meta:
fields = ['name', 'comment', 'albums']
model = Musician
Ок, итак, были разные ошибки, и вот некоторые подсказки, чтобы разделить проблему.
Сначала было две ошибки в сериализаторах, мы не должны сериализовать родителя artist, и мы должны добавить метод create в сериализатор Musician:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = 'title',
model = Album
class MusicianSerializer(serializers.ModelSerializer):
class Meta:
fields = 'id', 'name', 'comment', 'albums',
model = Musician
albums = AlbumSerializer(many=True)
def create(self, validated_data):
album_data = validated_data.pop('albums')
muse = Musician.objects.create(**validated_data)
for alb in album_data:
muse.albums.create(**alb)
return muse
Это можно проверить непосредственно в тестах, вот несколько подсказок как для сериализации отношений, так и косвенно для написания тестов о сериализаторе: https://www.django-rest-framework.org/api-guide/relations/ .
С его помощью я могу запустить скрипт django через команду runscript django-extension, как с rest_framework.test.RequestsClient, так и с requests.Session (в последнем случае мы запустим сервер командой runserver).
Сценарий может выглядеть следующим образом:
def run():
client = requests.Session()
client.auth = requests.auth.HTTPBasicAuth(username, password)
TEST_DATA = {
"name": "Miles Davis",
"comment": "Great Stuff",
"albums": [{"title": "B.B."}, {"title": "Don't know"}],
}
response = client.post(create_music_url, json=TEST_DATA)
Я нашел это полезным, чтобы понять, что код правильный и что тест был неправильным, который я все еще не могу заставить работать...
[Edit] Вот сообщения об ошибках в тесте с использованием rest_framework.test.APITestCase и его self.client и force_authenticate :
response = self.client.post(music_url, json=TEST_DATA)
response.data является следующим:
{'name': [ErrorDetail(string='This field is required.', code='required')], 'albums': [ErrorDetail(string='This field is required.', code='required')]}
и с
response = self.client.post(music_url, TEST_DATA)
Я получаю
{'albums': [ErrorDetail(string='This field is required.', code='required')]}
Имхо, эти сообщения об ошибках, мягко говоря, сбивают с толку...