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
- 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. - I hook in
create
ofCreateAPIView
and usewith transaction.atomic():
when writing to several tables at the same time to make sure all transactions are invalidated in case an error comes up. - 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.
Author by
Dibu Escobedo
Updated on June 26, 2022Comments
-
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()
andto_representation()
but the result was"non_field_errors": ["Invalid data. Expected a dictionary, but got list."]