Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework - create objects passed as a list attribute of nested object

Xzibit jokes aside, here's my model:

from django.db import models

class ProjectProfitAndLoss(models.Model):
    pass

class Component(models.Model):
    profit_and_loss = models.ForeignKey(ProjectProfitAndLoss, related_name='components')
    name = models.CharField(max_length=250)

class ComponentProductionVolume(models.Model):
    component = models.ForeignKey(Component, related_name='volumes')
    offset = models.IntegerField()
    volume = models.DecimalField(max_digits=16, decimal_places=4)

Serializers:

from rest_framework import serializers

class ComponentProductionVolumeSerializer(serializers.ModelSerializer):
    class Meta:
        model = ComponentProductionVolume


class ComponentSerializer(serializers.ModelSerializer):
    volumes = ComponentProductionVolumeSerializer(many=True, allow_add_remove=True)

    class Meta:
        model = Component


class ProjectProfitAndLossSerializer(serializers.ModelSerializer):
    components = ComponentSerializer(many=True, allow_add_remove=True)

    class Meta:
        model = ProjectProfitAndLoss

What I'm trying to do is post Components to be created as a list along with their ComponentProductionVolumes - also as lists. So my json looks something like this:

[
  {
    "name": "component 1",
    "profit_and_loss": 3,
    "volumes": [
      {
        "offset": 0,
        "volume": 2
      },
      {
        "offset": 1,
        "volume": 3
      },
      {
        "offset": 2,
        "volume": 2
      },
    ]
  },
  {
    "name": "component 2"
    "profit_and_loss": 3,
    "volumes": [
      {
        "offset": 0,
        "volume": 4
      },
      {
        "offset": 1,
        "volume": 2
      },
      {
        "offset": 2,
        "volume": 5
      },
    ]
  }
]

Unfortunately, what I'm getting back is a validation error:

components: [{volumes:[{component:[This field is required.]},{volumes:[{component:[This field is required.]} ... /* error repeated for each volume sent */ ]}] 

If I understand correctly, this errors tell me to include component id in each volume I send. But since I want DRF to create Components along with their volumes, this is not possible, because Components don't exist yet.

What would be the best approach to make DRF create components, and then ComponentProductionVolumes?

like image 551
Jacek Chmielewski Avatar asked Mar 19 '23 20:03

Jacek Chmielewski


1 Answers

DRF currently (version 2.3.13) has no built-in functionality to create nested relations. However, it is quite straight-forward to accomplish this by overriding create in a ListCreateView:

class ComponentList(generics.ListCreateAPIView):
    model = Component
    serializer_class = ComponentSerializer

    def create(self, request, *args, **kwargs):
        data = request.DATA

            # note transaction.atomic was introduced in Django 1.6
            with transaction.atomic():
                component = Component(
                    profit_and_loss=data['component_comments'],
                    name=data['name']
                )
                component.clean()
                component.save()

                for volume in data['volumes']:
                    ComponentProductionVolume.objects.create(
                        component=component,
                        offset=volume['offset'],
                        volume=volume['volume']
                    )

        serializer = ComponentSerializer(component)
        headers = self.get_success_headers(serializer.data)

        return Response(serializer.data, status=status.HTTP_201_CREATED,
                        headers=headers)

Note

The above code uses transaction.atomic which was introduced in Django 1.6. It comes in handy in this case as it rolls back changes if something goes awry. See the Django documentation about transactions for more info:

https://docs.djangoproject.com/en/dev/topics/db/transactions/

Also, this example creates one Component instance, but creating several can be accomplished by either modifying the client to send one Component POST request at a time or modifying the above code.

Hope this helps!

like image 86
Fiver Avatar answered Mar 22 '23 10:03

Fiver