3

I'm fairly new to Django and I'm trying to make a POST request with nested objects. This is the data that I'm sending:

{
   "id":null,
   "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,
   "tags":[
      {
         "id":null,
         "videoId":null,
         "videoTagId":42
      }
   ]
}

Here's a brief diagram of the relationship of these objects on the database

enter image description here

I want to create a video and pass in an array of nested data so that I can create multiple tags that can be associated to a video in a many to many relationship. Because of that, the 'id' field of the video will be null and the 'videoId' inside of the tag object will also be null when the data is being sent. However I keep getting a 400 (Bad request) error saying {tags: [{videoId: [This field may not be null.]}]}

I'm trying to override the create method inside VideoManageSerializer so that I can extract the tags and after creating the video I can use that video to create those tags. I don't think I'm even getting to the create method part inside VideoManageSerializer as the video is not created on the database. I've been stuck on this issue for a few days. If anybody could point me in the right direction I would really appreciate it.

I'm using the following serializers:

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__')

This is the view which uses 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()

These are all the models that I'm using:

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'
Marco Fregoso
  • 101
  • 1
  • 2
  • 9

1 Answers1

4

I would consider changing the serializer as below,

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

Things that have changed -

  1. Added a new write_only field named video_tag_id that supposed to accept "list of PKs of VideoTag".
  2. Changed the tags field to read_only so that it won't take part in the validation process, but you'll get the "nested serialized output".
  3. Changed create(...) method to cooperate with the new changes.
  4. The POST payload has been changed as below (note that tags has been removed and video_tag_id has been introduced)
    {
       "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: Simple foreign key assignment with nested serializer?
  2. DRF - write_only
  3. DRF - read_only
JPG
  • 82,442
  • 19
  • 127
  • 206