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.
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.
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.
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).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With