Home > Mobile >  How can I serialize one to many models in Django Rest?
How can I serialize one to many models in Django Rest?

Time:05-05

So I have two models Invoice and Items. Invoices can have many Items. Here's how they are defined:

class Invoice(models.Model):
    customer_name = models.CharField(max_length = 200, verbose_name='Customer Name')
    customer_phone = models.IntegerField(null = True, blank = True, verbose_name='Customer Phone')
    customer_address = models.TextField(null = True, blank = True, verbose_name='Customer Address')
    invoice_id = models.UUIDField(primary_key = True, unique = True, default=uuid.uuid4, verbose_name='Invoice ID')
    invoice_date = models.DateField(auto_now_add=True, verbose_name='Invoice Date')

    def __str__(self):
        return self.customer_name   ' - '   str(self.invoice_id)
    
    class Meta:
        verbose_name = 'Invoice'
        verbose_name_plural = 'Invoices'


class Items(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete = models.CASCADE, related_name='invoice')
    item_name = models.CharField(max_length = 200, null = False)
    item_quantity = models.IntegerField()
    item_price = models.IntegerField()
    item_id = models.AutoField(primary_key=True)

    def __str__(self):
        return self.item_name

    class Meta:
        verbose_name = 'Items'
        verbose_name_plural = 'Items'

I want to implement two API endpoints, one to get the list of invoices, and another to get a specific invoice with its items.

For example, /api/ will return all invoices

[
    {
        "customer_name": "John Doe",
        "customer_phone": 111666,
        "customer_address": "Palo Alto, California",
        "invoice_id": "a8aeb5a8-5498-40fd-9b4f-bb09a7057c71",
        "invoice_date": "2022-05-04"
    },
    {
        "customer_name": "Cassian Green",
        "customer_phone": 111000,
        "customer_address": "2112 Illinois Avenue",
        "invoice_id": "7d7b7878-3ffc-4dd2-a60a-fa207c147860",
        "invoice_date": "2022-05-04"
    },
    {
        "customer_name": "Chelsea Davis",
        "customer_phone": 666777,
        "customer_address": "2260 Coplin Avenue",
        "invoice_id": "3dda2054-49d7-49dc-9eba-ddc0fdacfd3b",
        "invoice_date": "2022-05-04"
    },
    {
        "customer_name": "Derek Elliott",
        "customer_phone": 111100,
        "customer_address": "3236 Sundown Lane",
        "invoice_id": "da112c0d-aff4-43d3-a465-910cc1483fc5",
        "invoice_date": "2022-05-04"
    }
]

and now if I request a specific invoice with its items, like /api/a8aeb5a8-5498-40fd-9b4f-bb09a7057c71 should return the following response

{
    "invoice_id": "a8aeb5a8-5498-40fd-9b4f-bb09a7057c71",
    "items": [
        {
            "item_name": "PC Cabinet",
            "item_quantity": 1,
            "item_price": 200
        },
        {
            "item_name": "Motherboard",
            "item_quantity": 1,
            "item_price": 1000
        },
        {
            "item_name": "PSU",
            "item_quantity": 1,
            "item_price": 300
        }
    ]
}

Here are the serializers:

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Items
        fields = [
            'item_name',
            'item_quantity',
            'item_price',
        ]

class InvoiceSerializer(serializers.ModelSerializer):
    items = ItemSerializer(many=True, read_only=True)

    class Meta:
        model = Invoice
        fields = [
            'customer_name',
            'customer_phone',
            'customer_address',
            'invoice_id',
            'invoice_date',
            'items'
        ]

And the views:

@api_view(['GET',])
def InvoiceList(request):
    if request.method == 'GET':
        queryset = Invoice.objects.all()
        serializer_class = InvoiceSerializer(queryset, many=True)
        return Response(serializer_class.data)


@api_view(['GET',])
def InvoiceDetail(request, pk):
    if request.method == 'GET':
        queryset = list(Items.objects.filter(invoice = pk))
        serializer_class = ItemSerializer(instance=queryset, many=True)
        return Response(serializer_class.data)

The first API endpoint is working as expected but the I need help with implementing the second one. How can I get that nested response?

Thanks.

CodePudding user response:

Your related_name of invoice in Items model is wrong, you should set it as "items" because you are going to call that from Invoice model like that.

Also if you want to have different response from that second API you should change it to use other serializer than InvoiceList:

@api_view(['GET',])
def InvoiceDetail(request, pk):
    if request.method == 'GET':
        queryset = list(Items.objects.filter(invoice = pk))
        serializer_class = InvoiceDetailSerializer(instance=queryset, many=True)
        return Response(serializer_class.data)

I changed name of serializer to InvoiceDetailSerializer so you need to create new serializer:

class InvoiceDetailSerializer(serializers.ModelSerializer):
    items = ItemSerializer(many=True, read_only=True)

    class Meta:
        model = Invoice
        fields = [
            'invoice_id',
            'items'
        ]

CodePudding user response:

you can try something like this. (not tested)

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Items
        fields = ['item_name', 'item_quantity', 'item_price',]


class InvoiceDetailsSerializer(serializers.ModelSerializer):
    items = ItemSerializer(many=True, read_only=True)

    class Meta:
        model = Invoice
        fields = ['id', 'items']
    
    
@api_view(['GET',])
def InvoiceDetail(request, pk):
    if request.method == 'GET':
        invoice = Invoice.objects.get(id=pk) 
        serializer_class = InvoiceDetailsSerializer(instance=invoice)
        return Response(serializer_class.data)
  • Related