DRF - Serializer Multiple Models

10,458

If this issue happens during a POST request, it means that you need to adapt the way you save your data. Django REST Framework doesn't support writing nested objects in DB out of the box.

What I usually do for these use cases is

  1. I don't use viewset for such complex use cases, instead I prefer using CreateAPIView which enables me to use specific serializers to validate inputs and to present data.
  2. I hook in create of CreateAPIView and use with transaction.atomic(): when writing to several tables at the same time to make sure all transactions are invalidated in case an error comes up.
  3. I use 2 serializers one for the parent model and one for the child model.

In your case the code could like this:

serializer.py

class PromoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Promocion
        fields = ('campaign', 'campaignName', 'promotionType', 'start_date', 'end_date', 'active')

class ItemPromoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = ('item_nbr', 'plu')

viewsets.py

from rest_framework import status
from django.db import transaction

class PromoCreateAPI(CreateAPIView):
    queryset = Promocion.objects.all()
    serializer_class = PromoSerializer

    # We skip perform_create
    def create(self, request, *args, ***kwargs):
        try:
            items_data = request.data.pop('items')
        except KeyError:
            return Response({}, status=status.HTTP_400_BAD_REQUEST)

        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        with transaction.atomic():
            instance = serializer.save()
            # Validate each item
            for item in items_data:
                s = ItemPromoSerializer(data=item)
                s.is_valid(raise_exception=True)
                s.save(campaign=instance)
        headers = self.get_success_headers(serializer.data)
        serializer.data['items'] = items_data
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Of course this code is untested, but I hope it helps you get where you need to.

Share:
10,458
Dibu Escobedo
Author by

Dibu Escobedo

Updated on June 26, 2022

Comments

  • Dibu Escobedo
    Dibu Escobedo almost 2 years

    How can I POST this JSON

    {
      "campaign": 27,
      "campaignName": "Prueba promo",
      "promotionType": 999,
      "items": [
         { "item_nbr": 1234567890123, "plu": 2},
         { "item_nbr": 12345678901, "plu": 3}
      ]
    }
    

    Currently, I only get this response JSON

    {
    "items": [],
    "campaign": 27,
    "campaignName": "Prueba promo",
    "promotionType": 999,
    "start_date": "2019-03-04T12:02:16.574874-03:00",
    "end_date": null,
    "active": true
    }
    

    How can I do it? I read the DRF documentation but it didn't work, what I'm doing wrong? here is my code

    my models.py

    class Item(models.Model):
        promocion = models.ForeignKey(Promocion, related_name='items', on_delete=models.CASCADE, null=True)
        item_nbr = models.IntegerField(primary_key=True, help_text="Numero de Item")
        modular = models.ForeignKey(Modular, on_delete=models.CASCADE, null=True)
        price = models.FloatField()
        q_min = models.PositiveIntegerField(default=1, help_text="Cantidad mínima")
        q_mul = models.PositiveIntegerField(default=1, help_text="Multiplo de cajas cerradas")
        vensil1 = models.CharField(max_length=30, help_text="Atributo item relevante")
        vensil2 = models.CharField(max_length=30, help_text="Atributo item relevante")
        vensil3 = models.CharField(max_length=30, help_text="Atributo item relevante")
        FG = "Fleje grande, 1/3 Carta"
        FP = "Fleje pequeño 1/6 Carta"
        CP = "Carteleria media Carta"
        opciones = ((FG, "Fleje grande, 1/3 Carta"),
                    (FP, "Fleje pequeño 1/6 Carta"),
                    (CP, "Carteleria media Carta"),)
        print_type = models.CharField(choices=opciones, help_text="Fleje a imprimir", max_length=255)
        depto = models.IntegerField(default=1, help_text="Departamento")
        descri = models.CharField(max_length=100, help_text="Descripción producto")
        brand = models.ForeignKey(Brand, on_delete=models.CASCADE, null=True)
        vendor_pack = models.IntegerField(default=1)
        container = models.CharField(max_length=6, default="MAY")
        size = models.CharField(max_length=20, help_text="Tamaño pack")
        cont_net = models.FloatField(default=1, help_text="Contenido Neto")
        sell_unit = models.CharField(max_length=5, help_text="Unidad de venta")
        weight_drain = models.FloatField(default=0, help_text="Peso drenado")
        cod_bal = models.IntegerField(null=True, blank=True, help_text="Código balanza")
        plu = models.BigIntegerField(help_text="Código de barra")
    

    here are my serializer.py

    class ItemPromoSerializer(serializers.ModelSerializer):
        class Meta:
            model = Item
            fields = ('item_nbr', 'plu')
    
    class PromoSerializer(serializers.ModelSerializer):
        items = ItemPromoSerializer(many=True, read_only=True)
        #steps = ScalePromoSerializer(many=True)
        class Meta:
             model = Promocion
             fields = ('items', 'campaign', 'campaignName', 'promotionType', 
         'start_date', 'end_date', 'active')
    

    my viewsets.py

    class PromoViewSet(viewsets.ModelViewSet):
        queryset = Promocion.objects.all()
        serializer_class = PromoSerializer
    

    and my routes.py

    router.register(r'promo', PromoViewSet)
    

    I've tried methods to_internal_value() and to_representation() but the result was

    "non_field_errors": ["Invalid data. Expected a dictionary, but got list."]