BigBoy1337
BigBoy1337

Reputation: 5023

Django Rest Framework: How to bulk_create nested objects during de-serialization?

I have three models:

class Customer(models.Model):
    name = models.CharField(max_length=30)

class Order(models.Model):
    customer = models.ForeignKey(Customer,on_delete=models.CASCADE)

class LineItem(models.Model):
    order = models.ForeignKey(Order,on_delete=models.CASCADE)
    name = models.CharField(max_length=30)

Here is my test:

class CreateOrdersTest(APITestCase):
    def setUp(self):
        self.TEST_SIZE = 10
        self.factory = APIRequestFactory()
        self._setup_source()
        self.data = self._build_data()

    def _setup_source(self):
        Customer.objects.create(name='test-customer')

    def _build_data(self):
        return [{'customer':'test-customer','lineitems':[{'name':'apples'},{'name':'oranges'}]} for x in range(self.TEST_SIZE)]

    def test_post_orders(self):

        request = self.factory.post('/create_orders',self.data)
        response = create_orders(request)
        response.render()
        self.assertEqual(response.status_code,status.HTTP_201_CREATED)

so the post object looks like

[{'customer': 'test-customer', 'lineitems': [{'name': 'apples'}, {'name': 'oranges'}]}, .... ] 

here are the serializers:

class BulkLineItemSerializer(serializers.ModelSerializer):
    def create(self,validated_data):
        lineitems = [LineItem(**validated_data) for item in validated_data]
        return LineItem.objects.bulk_create(**validated_data)

class LineItemSerializer(serializers.ModelSerializer):

    order = ModelObjectidField()

    def create(self,validated_data):
        return LineItem.objects.create(**validated_data)

    class Meta:
        model = LineItem
        list_serializer_class = BulkLineItemSerializer
        fields = ['name','order']

class BulkOrderSerializer(serializers.ListSerializer):
    def create(self,validated_data):
        orders = [Order(**item) for item in validated_data]
        return Order.objects.bulk_create(orders)

class OrderSerializer(serializers.ModelSerializer):

    customer = serializers.SlugRelatedField(slug_field='name',queryset=Customer.objects.all())

    def create(self,validated_data):
        return Order.objects.create(**validated_data)
    
    class Meta:
        model = Order
        fields = ['customer']
        list_serializer_class = BulkOrderSerializer

then here is that ModelObjectidField I use for the order object.

class ModelObjectidField(serializers.Field):

    def to_representation(self, value):
        return value.id

    def to_internal_value(self, data):
        return data

Because I am passing the actual object into the field like <Order(1)>, I just return it directly for the internal_value.

And finally here is my view. This is how I match the lineitems to the orders.

@api_view(['POST'])
def create_orders(request):
    if request.method == 'POST':
        fixed_orders = []
        lineitems_matching = []
        offset = 0
        lineitems_data = []
        for order in request.data:
            order_lineitems = order['lineitems']
            lineitems_data.extend(order_lineitems)
            for item in order_lineitems:
                lineitems_matching.append(offset)
            offset += 1
            fixed_orders.append(order)
        orderSerializer = OrderSerializer(data=fixed_orders,many=True)
        if orderSerializer.is_valid():
            orders = orderSerializer.save()
            fixed_lineitems = []
            offset = 0
            for lineitem_data in lineitems_data:
                lineitem_data['order'] = orders[lineitems_matching[offset]]
                fixed_lineitems.append(lineitem_data)
                offset += 1
            lineItemSerializer = LineItemSerializer(data=fixed_lineitems, many=True) # this line is getting the error
            if lineItemSerializer.is_valid():
                lineItemSerializer.save()
                return Response(orderSerializer.data,status=status.HTTP_201_CREATED)
            return Response(lineItemSerializer.errors,status=status.HTTP_400_BAD_REQUEST)
        return Response(orderSerializer.errors,status=status.HTTP_400_BAD_REQUEST)

Currently I am getting this error:

(venv) alexmarshall@Alexs-MacBook-Pro spicy % ./manage.py test NestedOrderTest
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_post_orders (NestedOrderTest.tests.CreateOrdersTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/alexmarshall/src/GreenData/spicy/spicy/NestedOrderTest/tests.py", line 23, in test_post_orders
    response = create_orders(request)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/decorators.py", line 50, in handler
    return func(*args, **kwargs)
  File "/Users/alexmarshall/src/GreenData/spicy/spicy/NestedOrderTest/views.py", line 31, in create_orders
    lineItemSerializer = LineItemSerializer(data=fixed_lineitems, many=True)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 121, in __new__
    return cls.many_init(*args, **kwargs)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 158, in many_init
    return list_serializer_class(*args, **list_kwargs)
  File "/Users/alexmarshall/src/GreenData/spicy/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 115, in __init__
    super().__init__(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'child'

----------------------------------------------------------------------
Ran 1 test in 0.010s

FAILED (errors=1)

I think the problem is that I am passing the lineitem serializer data that has the order itself. Here is what the fixed_lineitems data looks like:

[{'name': 'apples', 'order': <Order: Order object (1)>}, ... ]  

Thats what I am using the custom field for, but It doesn't seem to like it? I don't really know what that TypeError means. Why would it be getting a child? But really Im just not sure how to setup the serializers in this case. Any help?

Edit: I changed the lineitem serializer to this and I believe its working!

class BulkLineItemSerializer(serializers.ListSerializer):
    def create(self,validated_data):
        lineitems = [LineItem(**item) for item in validated_data]
        return LineItem.objects.bulk_create(lineitems)

Upvotes: 0

Views: 1019

Answers (1)

jules-ch
jules-ch

Reputation: 119

Look at BulkLineItemSerializer, it must inherit from ListSerializer. It is needed to handle passing list to it with many=True

https://www.django-rest-framework.org/api-guide/serializers/#listserializer

Like so:

class BulkLineItemSerializer(serializers.ListSerializer):
    ...

Upvotes: 2

Related Questions