4

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?

2 Answers 2

4

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!

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your answer. I have already solved this problem, but in a slightly different way. I overrode restore_object method on ComponentSerializer and then did pretty much the same thing you did there, but used ComponentProductionVolumeSerializer(data=data['volumes'], many=True) to deserialize all volumes in one pass - this gives me validation errors for each individual volume. I was looking for something like transaction.atomic() that you used - will definitely use that alot now.
@TomChristie Was this fixed in 2.3.14?
@AlexRothberg Looking at the 2.3.14 release notes it doesn't seem so. I believe writable nested functionality is planned for the 2.4 release, but you could ask Tom on the google group
Within the transaction I see the Component and ComponentProductionVolume are being manually created, is there any reason why the Serializers for these models couldn't be used as they were in the original implementation?
3

Answer for updating question context

Currently in DRF 3.1, this functionality is supported, you can see full docs here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.