Django RestFramework POST nested request

I'm having a little problem right now with Django Rest Framework. I'm trying to post an object with nested objects in it.

This is my models.py file.

class Supplier(models.Model):
    name = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return self.name
class Transaction(models.Model):
    date = models.CharField(max_length=255, blank=True, null=True)
    income = models.IntegerField(blank=True,null=True)
    expense = models.IntegerField(blank=True,null=True)
    card = models.CharField(max_length=255, blank=True, null=True)
    currency = models.CharField(max_length=255, blank=True, null=True)
    supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE)
    category = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return self.date + ' ' + str(self.expense) + ' ' + self.card + ' ' + str(self.supplier)

This is my serializers.py file

class SupplierSerializer(serializers.ModelSerializer):
    class Meta:
        model = Supplier
        fields = ['id', 'name']


class TransactionSerializer(serializers.ModelSerializer):
    supplier = SupplierSerializer()
    class Meta:
        model = Transaction
        fields = ['id',
                  'date',
                  'income',
                  'expense',
                  'card',
                  'currency',
                  'supplier',
                  'category']
        read_only_fields = ['id']

    def create(self, validated_data):
        supplier_data = validated_data.pop('supplier')
        supplier = Supplier.objects.create(**supplier_data)
        transaction = Transaction.objects.create(supplier=supplier, **validated_data)

        return transaction

This is my views.py file

@api_view(['POST', 'GET'])
def transaction(request):

    if request.method == 'POST':
        transaction_serializer = TransactionSerializer(data=request.data)
        if transaction_serializer.is_valid():
            transaction_serializer.save()
            return Response(transaction_serializer.data, status=status.HTTP_201_CREATED)

    if request.method == 'GET':
        transactions = Transaction.objects.all()
        serializer = TransactionSerializer(transactions, many = True)
        return JsonResponse(serializer.data, safe=False)

I'm trying to make this POST Request:

{
    "date": "12/10/2022",
    "income": 41241,
    "expense": null,
    "card": "Credit card",
    "currency": "CLP",
    "supplier": [{
      "name": "Cabify"
    }],
    "category": "Transporte"
}

But in console show me this error:

[28/Nov/2022 19:50:56] "POST /transactions/ HTTP/1.1" 500 73587
Internal Server Error: /transactions/
Traceback (most recent call last):
  File "/Users/benjaminandaur/Desktop/projects/drinks-api/venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/Users/benjaminandaur/Desktop/projects/drinks-api/venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 191, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/benjaminandaur/Desktop/projects/drinks-api/venv/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/benjaminandaur/Desktop/projects/drinks-api/venv/lib/python3.9/site-packages/django/views/generic/base.py", line 103, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/benjaminandaur/Desktop/projects/drinks-api/venv/lib/python3.9/site-packages/rest_framework/views.py", line 513, in dispatch
    self.response = self.finalize_response(request, response, *args, **kwargs)
  File "/Users/benjaminandaur/Desktop/projects/drinks-api/venv/lib/python3.9/site-packages/rest_framework/views.py", line 424, in finalize_response
    assert isinstance(response, HttpResponseBase), (
AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`
[28/Nov/2022 19:54:33] "POST /transactions/ HTTP/1.1" 500 73587

from the DRF api_view decorator documentation, the view would be something like this:

@api_view(['GET', 'POST'])
def transaction(request):
    if request.method == 'POST':
        transaction_serializer = TransactionSerializer(data=request.data)
        if transaction_serializer.is_valid():
            transaction_serializer.save()
        return Response({"status": status.HTTP_201_CREATED, "data": serializer.data})
    
    transactions = Transaction.objects.all()
    serializer = TransactionSerializer(transactions, many = True)
    return Response({"data": serializer.data})

I think there’s a serialization problem so you need to treat case your serializer is not valid like that that to get the source of the problem:

@api_view(['POST', 'GET'])
def transaction(request):

    if request.method == 'POST':
        transaction_serializer = TransactionSerializer(data=request.data)
        if transaction_serializer.is_valid():
            transaction_serializer.save()
            return Response(transaction_serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(transaction_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    if request.method == 'GET':
        transactions = Transaction.objects.all()
        serializer = TransactionSerializer(transactions, many = True)
        return JsonResponse(serializer.data, safe=False)

and i think that the problem with your serializer is that you send a list of suppliers

"supplier": [{
      "name": "Cabify"
    }]

and in your serializer you are creating just one, and in your model it's a ForeignKey so your serializer is correct referring to your model so i think you need to change your post request:

{
    "date": "12/10/2022",
    "income": 41241,
    "expense": null,
    "card": "Credit card",
    "currency": "CLP",
    "supplier": {
      "name": "Cabify"
    },
    "category": "Transporte"
}

or if you dont have the control of the POST request

you can change your serializer create method and pick juste the first element of suppliers list like that:

class TransactionSerializer(serializers.ModelSerializer):
    supplier = SupplierSerializer()
    class Meta:
        model = Transaction
        fields = ['id',
                  'date',
                  'income',
                  'expense',
                  'card',
                  'currency',
                  'supplier',
                  'category']
        read_only_fields = ['id']

    def create(self, validated_data):
        supplier_data = validated_data.pop('supplier')
        if supplier_data:
            supplier = Supplier.objects.create(**supplier_data[0])
        transaction = Transaction.objects.create(supplier=supplier, **validated_data)

        return transaction
Back to Top