Home > Back-end >  How to save a nested object in a post request correctly?
How to save a nested object in a post request correctly?

Time:02-04

I am trying to work with a post request were I am first saving a Tag object, which then becomes the tag field of a Tagging object. However, no matter what combinations I have tried, despite the Tag Post method working, when I pass a json object like this:

{
        "user_id": 1,
        "gameround_id": 2015594866,
        "resource_id": 2975,
        "tag": {
            "name": "TESTTAGGGG2222",
            "language": "en"
        },
        "score": 0,
        "origin": ""
    }

I keep getting this message:

{
    "name": [
        "This field is required."
    ],
    "language": [
        "This field is required."
    ]
}

However, when I pass a Tag, it works.

This is the post method:

   def post(self, request, *args, **kwargs):
        if not isinstance(request.user, CustomUser):
            current_user_id = 1
        else:
            current_user_id = request.user.pk
        gameround = request.data.get('gameround', '')
        random_resource = request.data.get('resource', '')
        created = datetime.now()
        score = 0
        origin = ''
        name = request.data.get('name', '')
        language = request.data.get('language', '')

        tag_serializer = TagSerializer(data=request.data)
        tagging_serializer = TaggingSerializer(data=request.data)

        if tag_serializer.is_valid(raise_exception=True):
            tag_serializer.save(tag=request.data)
            if tagging_serializer.is_valid(raise_exception=True):
                tagging_serializer.save(tagging=request.data, tag=tag_serializer.data)
                return Response({"status": "success", "data": tagging_serializer.data},
                                status=status.HTTP_201_CREATED)
            # else:
            # return Response({"status": "success", "data": tag_serializer.data},status=status.HTTP_201_CREATED)
        else:
            return Response({"status": "error", "data": tag_serializer.errors},
                            status=status.HTTP_400_BAD_REQUEST)

How do I correctly pass the nested object in the post method so that I don't get this error anymore?

models.py

class Tag(models.Model):
    name = models.CharField(max_length=256)
    language = models.CharField(max_length=256)

    objects = models.Manager()

    def __str__(self):
        return self.name or ''

    @property
    def tags(self):
        tags = self.tagging.values('tag')
        return tags.values('tag_id', 'tag__name', 'tag__language')


class Tagging(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
    gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
    resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name='tagging')
    created = models.DateTimeField(editable=False, null=True)
    score = models.PositiveIntegerField(default=0)
    # media_type = models.ForeignKey(Gamemode, on_delete=models.CASCADE)
    origin = models.URLField(max_length=256, blank=True, default='')

    objects = models.Manager()

    def __str__(self):
        return str(self.tag) or ''

serializers.py

class TagSerializer(serializers.ModelSerializer):

  class Meta:
    model = Tag
    fields = ('name', 'language')

  def create(self, validated_data):
    tag_data = validated_data.pop('tag')
    Tag.objects.create(**tag_data)
    return tag_data

  def to_representation(self, data):
    data = super().to_representation(data)
    return data

class TaggingSerializer(serializers.ModelSerializer):
  tag = TagSerializer(required=False, write_only=False)
  resource_id = serializers.PrimaryKeyRelatedField(queryset=Resource.objects.all(),
                                                   required=True,
                                                   source='resource',
                                                   write_only=False)
  gameround_id = serializers.PrimaryKeyRelatedField(queryset=Gameround.objects.all(),
                                                    required=False,
                                                    source='gameround',
                                                    write_only=False)
  user_id = serializers.PrimaryKeyRelatedField(queryset=CustomUser.objects.all(),
                                               required=False,
                                               source='user',
                                               write_only=False)

  class Meta:
    model = Tagging
    fields = ('id', 'user_id', 'gameround_id', 'resource_id', 'tag', 'created', 'score', 'origin')
    depth = 1

  def create(self, validated_data):
    """Create and return a new tagging"""

    tag_data = validated_data.pop('tag', None)
    if tag_data:
      tag = Tag.objects.get_or_create(**tag_data)[0]
      validated_data['tag'] = tag

    tagging = Tagging(
      user=validated_data.get("user"),
      gameround=validated_data.get("gameround"),
      resource=validated_data.get("resource"),
      tag=validated_data.get("tag"),
      created=datetime.now(),
      score=validated_data.get("score"),
      origin=validated_data.get("origin")
    )

    tagging.save()
    return tagging

  def to_representation(self, instance):
    rep = super().to_representation(instance)
    rep['tag'] = TagSerializer(instance.tag).data
    return rep

CodePudding user response:

In your post method, you made

tag_serializer = TagSerializer(data=request.data)
if tag_serializer.is_valid(raise_exception=True):
    # the rest of the code

this means that your data will pass to TagSerializer so DRF will check if the fields for this serializer are in the request body or not, and it's not provided, cause this data does not belong to this serializer, it belongs to TaggingSerializer so this will give you an error This field is required

So, you need to send your request data only to TaggingSerializer, try that and let's see what will happen, and I will suggest using serializers.Serializer instead of using serializers.ModelSerializer for better performance

  •  Tags:  
  • Related