Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django-Rest-Framework. Updating nested object

I'm having a problem of updating a nested object.

So I have a model which structure is similar to this one:

class Invoice(models.Model):     nr = models.CharField(max_length=100)     title = models.CharField(max_length=100)  class InvoiceItem(models.Model):     name = models.CharField(max_length=100)     price = models.FloatField()     invoice = models.ForeignKey(Invoice, related_name='items') 

I need to create child objects from parent, and what I mean by that, is to create InvoiceItems directly when creating an Invoice object. For this purpose, I've wrote the following serializers:

class InvoiceItemSerializer(serializers.ModelSerializer):     invoice = serializers.PrimaryKeyRelatedField(queryset=Invoice.objects.all(), required=False)     class Meta:         model = InvoiceItem   class InvoiceSerializer(serializers.ModelSerializer):     items = InvoiceItemSerializer(many=True)      class Meta:         model = Invoice      def create(self, validated_data):         items = validated_data.pop('items', None)         invoice = Invoice(**validated_data)         invoice.save()         for item in items:             InvoiceItem.objects.create(invoice=invoice, **item)         return invoice 

Up till now, the create/read/delete methods work perfectly, except the update. I think the below logic should be correct, but it misses something.

def update(self, instance, validated_data):     instance.nr = validated_data.get('nr', instance.nr)     instance.title = validated_data.get('title', instance.title)     instance.save()      # up till here everything is updating, however the problem appears here.     # I don't know how to get the right InvoiceItem object, because in the validated     # data I get the items queryset, but without an id.      items = validated_data.get('items')     for item in items:         inv_item = InvoiceItem.objects.get(id=?????, invoice=instance)         inv_item.name = item.get('name', inv_item.name)         inv_item.price = item.get('price', inv_item.price)         inv_item.save()      return instance 

Any help would be really appreciated.

like image 827
dimmg Avatar asked May 15 '16 16:05

dimmg


People also ask

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

Do we need Serializers in Django REST Framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

What is serializing in Django?

Django's serialization framework provides a mechanism for “translating” Django models into other formats. Usually these other formats will be text-based and used for sending Django data over a wire, but it's possible for a serializer to handle any format (text-based or not).


2 Answers

This is the way I've accomplished the task:

I've added an id field to the InvoiceItemSerializer

class InvoiceItemSerializer(serializers.ModelSerializer):     ...     id = serializers.IntegerField(required=False)     ... 

And the update method for the InvoiceSerializer

def update(self, instance, validated_data):     instance.nr = validated_data.get('nr', instance.nr)     instance.title = validated_data.get('title', instance.title)     instance.save()      items = validated_data.get('items')      for item in items:         item_id = item.get('id', None)         if item_id:             inv_item = InvoiceItem.objects.get(id=item_id, invoice=instance)             inv_item.name = item.get('name', inv_item.name)             inv_item.price = item.get('price', inv_item.price)             inv_item.save()         else:             InvoiceItem.objects.create(account=instance, **item)      return instance 

Also in the create method I'm popping the id if it is passed.

like image 60
dimmg Avatar answered Sep 29 '22 13:09

dimmg


All of these solutions seemed too complex or too specific for me, I ended up using code from the tutorial here which was incredibly simple and reusable:

from rest_framework import serializers from django.contrib.auth import get_user_model from myapp.models import UserProfile   # You should already have this somewhere class UserProfileSerializer(serializers.ModelSerializer):     class Meta:         model = UserProfile         fields = ['nested', 'fields', 'you', 'can', 'edit']   class UserSerializer(serializers.ModelSerializer):     # CHANGE "userprofile" here to match your one-to-one field name     userprofile = UserProfileSerializer()      def update(self, instance, validated_data):         # CHANGE "userprofile" here to match your one-to-one field name         if 'userprofile' in validated_data:             nested_serializer = self.fields['userprofile']             nested_instance = instance.userprofile             nested_data = validated_data.pop('userprofile')              # Runs the update on whatever serializer the nested data belongs to             nested_serializer.update(nested_instance, nested_data)          # Runs the original parent update(), since the nested fields were         # "popped" out of the data         return super(UserSerializer, self).update(instance, validated_data) 

EDIT: Bugfix, I added a check for the nested field's existence before attempting to update it.

like image 37
Preston Badeer Avatar answered Sep 29 '22 11:09

Preston Badeer