Django - Отправка post-запроса с использованием вложенного сериализатора с отношениями "многие ко многим". Получение [400 код ошибки] "Это поле не может быть null] ошибка"

Я довольно новичок в Django и пытаюсь сделать POST запрос с вложенными объектами. Вот данные, которые я отправляю:

{
   "id":null,
   "deleted":false,
   "publishedOn":2022-11-28,
   "decoratedThumbnail":"https"://i.ytimg.com/vi/a2lC4V479hQ/maxresdefault.jpg,
   "rawThumbnail":"https"://i.ytimg.com/vi/a2lC4V479hQ/maxresdefault.jpg,
   "videoUrl":"https"://www.youtube.com/watch?v=cl60ToTvG8w,
   "title":"Video with tags",
   "duration":120,
   "visibility":1,
   "tags":[
      {
         "id":null,
         "videoId":null,
         "videoTagId":42
      }
   ]
}

Вот краткая диаграмма взаимоотношений этих объектов в базе данных

enter image description here

Я хочу создать видео и передать массив вложенных данных, чтобы я мог создать несколько тегов, которые могут быть связаны с видео в отношениях "многие ко многим". В связи с этим поле 'id' видео будет равно нулю, и 'videoId' внутри объекта тега также будет равно нулю при передаче данных. Однако я продолжаю получать ошибку 400 (Bad request), говорящую {tags: [{videoId: [This field may not be null.]}]}

Я пытаюсь переопределить метод create внутри VideoManageSerializer, чтобы я мог извлечь теги и после создания видео использовать это видео для создания этих тегов. Мне кажется, что я даже не дохожу до части метода create внутри VideoManageSerializer, поскольку видео не создается в базе данных. Я застрял на этом вопросе уже несколько дней. Если кто-нибудь может направить меня в правильном направлении, я буду очень признателен.

Я использую следующие сериализаторы:

class VideoManageSerializer(serializers.ModelSerializer):
    tags =  VideoVideoTagSerializer(many=True)

    class Meta:
        model = Video
        fields = ('__all__')
    
    # POST
    def create(self, validated_data):
        tags = validated_data.pop('tags')
        video_instance = Video.objects.create(**validated_data)
        for tag in tags:
            VideoVideoTag.objects.create(video=video_instance, **tag)
        return video_instance

class VideoVideoTagSerializer(serializers.ModelSerializer):
    class Meta:
        model = VideoVideoTag
        fields = ('__all__')

Это представление, которое использует VideoManageSerializer

class VideoManageViewSet(GenericViewSet,  # generic view functionality
                 CreateModelMixin,  # handles POSTs
                 RetrieveModelMixin,  # handles GETs 
                 UpdateModelMixin,  # handles PUTs and PATCHes
                 ListModelMixin):
    serializer_class = VideoManageSerializer
    queryset = Video.objects.all()

Вот все модели, которые я использую:

class Video(models.Model):
    decoratedThumbnail = models.CharField(max_length=500, blank=True, null=True)
    rawThumbnail = models.CharField(max_length=500, blank=True, null=True)
    videoUrl = models.CharField(max_length=500, blank=True, null=True)
    title = models.CharField(max_length=255, blank=True, null=True)
    duration = models.PositiveIntegerField()
    visibility = models.ForeignKey(VisibilityType, models.DO_NOTHING, related_name='visibility')
    publishedOn = models.DateField()
    deleted = models.BooleanField(default=0)

    class Meta:
        managed = True
        db_table = 'video'

class VideoTag(models.Model):
    name = models.CharField(max_length=100, blank=True, null=True)
    deleted = models.BooleanField(default=0)

    class Meta:
        managed = True
        db_table = 'video_tag'

class VideoVideoTag(models.Model):
    videoId = models.ForeignKey(Video, models.DO_NOTHING, related_name='tags', db_column='videoId')
    videoTagId = models.ForeignKey(VideoTag, models.DO_NOTHING, related_name='video_tag', db_column='videoTagId')

    class Meta:
        managed = True
        db_table = 'video_video_tag'

Я бы рассмотрел возможность изменения сериализатора следующим образом,

class VideoManageSerializer(serializers.ModelSerializer):
    video_tag_id = serializers.PrimaryKeyRelatedField(
        many=True,
        queryset=VideoTag.objects.all(),
        write_only=True,
    )
    tags = VideoVideoTagSerializer(many=True, read_only=True)

    class Meta:
        model = Video
        fields = "__all__"

    # POST
    def create(self, validated_data):
        tags = validated_data.pop("video_tag_id")
        video_instance = Video.objects.create(**validated_data)
        for tag in tags:
            VideoVideoTag.objects.create(videoId=video_instance, videoTagId=tag)
        return video_instance

Вещи, которые изменились -

  1. Добавил новое поле write_only с именем video_tag_id, которое должно было принимать "список PKs из VideoTag" .
  2. Изменили поле tags на read_only так, что оно не будет участвовать в процессе валидации, но вы получите "вложенный сериализованный вывод".
  3. Changed create(...) метод для взаимодействия с новыми изменениями.
  4. Полезная нагрузка POST была изменена следующим образом (обратите внимание, что tags была удалена, а video_tag_id была введена)
    {
       "deleted":false,
       "publishedOn":"2022-11-28",
       "decoratedThumbnail":"https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg",
       "rawThumbnail":"https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg",
       "videoUrl":"https://www.youtube.com/watch?v=jNQXAC9IVRw",
       "title":"Video with tags",
       "duration":120,
       "visibility":1,
       "video_tag_id":[1,2,3]
    }
    

Refs

  1. DRF: Простое присвоение внешнего ключа с вложенным сериализатором?
  2. DRF - write_only
  3. ДРФ - read_only
Вернуться на верх