3👍
At the point in the trace where I try to access serializer.data
and get the KeyError, I note that serializer.data
only contains key/vaule pairs from the initial_data
, not the instance data (hence, I suppose, the KeyError; some model fields’ keys are not present as it is a partial_update
request). However, serializer.child.data
does contain all the instance data for the last child in the list.
So, I go to the rest_framework/serializers.py
source where data
is defined:
249 @property
250 def data(self):
251 if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
252 msg = (
253 'When a serializer is passed a `data` keyword argument you '
254 'must call `.is_valid()` before attempting to access the '
255 'serialized `.data` representation.\n'
256 'You should either call `.is_valid()` first, '
257 'or access `.initial_data` instead.'
258 )
259 raise AssertionError(msg)
260
261 if not hasattr(self, '_data'):
262 if self.instance is not None and not getattr(self, '_errors', None):
263 self._data = self.to_representation(self.instance)
264 elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
265 self._data = self.to_representation(self.validated_data)
266 else:
267 self._data = self.get_initial()
268 return self._data
Line 265 is problematic. I can replicate the error by calling serializer.child.to_representation({'uuid': '87956604-fbcb-4244-bda3-9e39075d510a', 'product_code': 'foobar'})
at the breakpoint.
Calling partial_update()
works fine on a single instance (because self.instance
is set, self.to_representation(self.instance)
works). However, for a bulk partial_update() implementation, self.validated_data
is missing model fields, and to_representation()
won’t work, so I won’t be able to access the .data
property.
One option would be to maintain some sort of self.instances
list of Product instances, and override the definition of data
on line 265:
self._data = self.to_representation(self.instances)
I’d really prefer an answer from someone more experienced in this sort of problem though, as I’m not sure if that’s a sensible solution, hence I’m leaving the bounty open in the hope that someone can suggest something smarter to do.
9👍
As mentioned in the comment i still think the exception could be because of the user field in BulkProductSerializer class, not really anything to do with ListSerializer
There might be another minor error (but important) in the serializer DRF as mentioned in the documentation here. Here is how to specify a list_serializer_class
:
class CustomListSerializer(serializers.ListSerializer):
...
class CustomSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = CustomListSerializer
Note that it’s specified inside of the Meta
class, not outside. So i think in your code, it will not understand to switch to the List Serializer with many=True
. That should cause the not-updating problem.
Update – Add example for updating nested list serializer
It seems that the question was more about a generic way to implement updating for nested List Serializer rather than the actual error. Therefore, i will try to provide a sample code.
Some notes:
- If we use ModelViewSet, the list route won’t allow PUT or PATCH, so neither update nor partial_update will be called (reference). Therefore i use POST directly, this is much simpler.
- If you want to use PUT/PATCH, then see this answer here
- We can always add a query parameter like allow_update or partial directly to the Post request to differentiate between POST/PUT/PATCH
- Instead of using uuid like the question i will use the normal id, it should be very much the same
It was rather simple
For reference, models look like this:
class Product(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User, null=True, blank=True)
def __unicode__(self):
return self.name
Step 1: Make sure serializer change to ListSerializer
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
def get_serializer(self, *args, **kwargs):
# checking for post only so that 'get' won't be affected
if self.request.method.lower() == 'post':
data = kwargs.get('data')
kwargs['many'] = isinstance(data, list)
return super(ProductViewSet, self).get_serializer(*args, **kwargs)
Step 2: Implement the ListSerializer by overriding create function
class ProductListSerializer(serializers.ListSerializer):
def create(self, validated_data):
new_products = [Product(**p) for p in validated_data if not p.get('id')]
updating_data = {p.get('id'): p for p in validated_data if p.get('id')}
# query old products
old_products = Product.objects.filter(id__in=updating_data.keys())
with transaction.atomic():
# create new products
all_products = Product.objects.bulk_create(new_products)
# update old products
for p in old_products:
data = updating_data.get(p.id, {})
# pop id to remove
data.pop('id')
updated_p = Product(id=p.id, **data)
updated_p.save()
all_products.append(updated_p)
return all_products
class ProductSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all())
id = serializers.IntegerField(required=False)
class Meta:
model = Product
fields = '__all__'
list_serializer_class = ProductListSerializer
0👍
Your Error has nothing to do with ListSerializer
, but a problem getting field user
:
KeyError: "Got KeyError when attempting to get a value for field
user
on serializerBulkProductSerializer
.The serializer field might be named incorrectly and not match any attribute or key on the
OrderedDict
instance.Original exception text was: ‘fk_user’."
Make sure your Product
model has a fk_user
field.
You also have defined user
field on BulkProductSerializer
as writable but have not told the serializer how to handle it…
The easiest way to correct this is to use a SlugRelatedField:
class BulkProductSerializer(serializers.ModelSerializer):
list_serializer_class = CustomProductListSerializer
user = serializers.SlugRelatedField(
slug_field='username',
queryset=UserModel.objects.all(),
source='fk_user'
)
class Meta:
model = Product
fields = (
'user',
'uuid',
'product_code',
...,
)
This should handle nicely errors, for example when username
does not exist…
- How do I use request.META.get('HTTP_REFERER') within template?
- DJANGO: How to list_display a reverse foreign key attribute?
- Django "Enter a list of values" form error when rendering a ManyToManyField as a Textarea
- Django ORM: See if a model has no foreign key entry in another model
-1👍
Remove source if you are using Django auth model and set read_only=True.
user = serializers.CharField(read_only=True)
Hope this works for you
- Resize image maintaining aspect ratio AND making portrait and landscape images exact same size?
- Django admin panel deploy on server Forbidden (403) CSRF verification failed. Request aborted