54๐
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.
21๐
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.
- [Django]-Django Rest Framework โ APIView Pagination
- [Django]-Does django with mongodb make migrations a thing of the past?
- [Django]-Django {% if forloop.first %} question
10๐
I came across the same problem recently. The way I addressed it was to force the id
to be a required field:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('id', 'name', 'url', )
extra_kwargs = {'id': {'read_only': False, 'required': True}}
This way I was able to retrieve the correct instance and update it
- [Django]-How to produce a 303 Http Response in Django?
- [Django]-Django syncdb and an updated model
- [Django]-Setting default value for Foreign Key attribute in Django
7๐
I think Vitor Hugo Moralesโs answer is great and would like to contribute one cent of mine by looping through the keys to assign each field in the object to that in validated data rather than hard-coding it the way he has done. For example,
def update_product_items(self, instance, validated_data):
# get the nested objects list
product_items = validated_data.pop('products')
# get all nested objects related with this instance and make a dict(id, object)
product_items_dict = dict((i.id, i) for i in instance.products.all())
for item_data in product_items:
if 'id' in item_data:
# if exists id remove from the dict and update
product_item = product_items_dict.pop(item_data['id'])
# remove id from validated data as we don't require it.
item_data.pop('id')
# loop through the rest of keys in validated data to assign it to its respective field
for key in item_data.keys():
setattr(product_item,key,item_data[key])
product_item.save()
else:
# else create a new object
ProductItem.objects.create(product=instance, **item_data)
# delete remaining elements because they're not present in my update call
if len(product_items_dict) > 0:
for item in product_items_dict.values():
item.delete()
- [Django]-Running Django with FastCGI or with mod_python
- [Django]-You need to install postgresql-server-dev-X.Y for building a server-side extension or libpq-dev for building a client-side application
- [Django]-How to use Django ImageField, and why use it at all?
4๐
In my case I wish to update all list of nested objects even if theyโre deleted.
I donโt want to in every nested object delete, call the nested Model DELETE method; just update entire object and your nested object list.
For this implementation: 1-Product has N-ProductItems
def update_product_items(self, instance, validated_data):
# get the nested objects list
product_items = validated_data.pop('products')
# get all nested objects related with this instance and make a dict(id, object)
product_items_dict = dict((i.id, i) for i in instance.products.all())
for item_data in product_items:
if 'id' in item_data:
# if exists id remove from the dict and update
product_item = product_items_dict.pop(item_data['id'])
product_item.quantity = item_data['quantity']
product_item.size_pmg = item_data['size_pmg']
product_item.size_number = item_data['size_number']
product_item.color = item_data['color']
product_item.save()
else:
# else create a new object
ProductItem.objects.create(product=instance, **item_data)
# delete remaining elements because they're not present in my update call
if len(product_items_dict) > 0:
for item in product_items_dict.values():
item.delete()
- [Django]-Test sending email without email server
- [Django]-Django: Generic detail view must be called with either an object pk or a slug
- [Django]-Get javascript variable's value in Django url template tag
1๐
I would add transaction and use some util django methods
from django.db import transaction
def update(self, instance, validated_data):
with transaction.atomic():
items = validated_data.pop('items', None)
for key, value in validated_data.items():
setattr(instance, key, value)
instance.save()
for item in items:
inv_item, created = InvoiceItem.objects.update_or_create(id=item['id'], invoice=instance, defaults={**item})
return instance
- [Django]-Return the current user with Django Rest Framework
- [Django]-What is the right way to validate if an object exists in a django view without returning 404?
- [Django]-<Django object > is not JSON serializable
0๐
Try
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:
inv_item = InvoiceItem.objects.get(invoice=instance, pk=item.pk)
inv_item.name = item.get('name', inv_item.name)
inv_item.price = item.get('price', inv_item.price)
inv_item.invoice = instance
inv_item.save()
instance.save()
return instance
- [Django]-Customize/remove Django select box blank option
- [Django]-Make the first letter uppercase inside a django template
- [Django]-Django ModelForm with extra fields that are not in the model
0๐
Try this.
from rest_framework.utils import model_meta
class InvoiceSerializer(serializers.ModelSerializer):
invoice_item=InvoiceItemSerializer(many=True,required=False)
field_map={"invoice_item" : { "model": models.InvoiceItem
"pk_field" : "id"}}
class Meta:
model = models.Invoice
fields = '__all__'
def create(self, validated_data):
extra_data={}
for key in self.field_map.keys():
extra_data[key]=validated_data.pop(key,[])
# create invoice
invoice = models.Invoice.objects.create(**validated_data)
for key in extra_data.keys():
for data in extra_data[key]:
self.field_map[key]["model"].objects.create(invoice=invoice,**data)
return invoice
def _update(self,instance,validated_data):
#drf default implementation
info = model_meta.get_field_info(instance)
for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many:
field = getattr(instance, attr)
field.set(value)
else:
setattr(instance, attr, value)
instance.save()
return instance
def update(self,instance,validated_data):
extra_data={}
for key in self.field_map.keys():
extra_data[key]=validated_data.pop(key,[])
instance=self._update(instance,validated_data)
for key in extra_data.keys():
for data in extra_data[key]:
id=data.get(self.field_map[key]["pk_field"],None)
if id:
try:
related_instance=self.field_map[key]["model"].objects.get(id=id)
except:
raise
self._update(related_instance,data)
else:
self.field_map[key]["model"].objects.create(**data)
return instance
- [Django]-Select DISTINCT individual columns in django?
- [Django]-How to get the value of a Django Model Field object
- [Django]-Django: 'current_tags' is not a valid tag library
0๐
The drf-writable-nested package provides writable nested model serializer which allows to create/update models with nested related data.
- [Django]-ImproperlyConfigured: You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings
- [Django]-Django using get_user_model vs settings.AUTH_USER_MODEL
- [Django]-Using mock to patch a celery task in Django unit tests