In current implementation of DRF a 'partial' flag for each nested serializer is taken from the root serializer. There is no legal way to control 'partial' flag in the nested serializer. But there are situations where such possibility is needed. Example:
class NoteSerializer(serializers.Serializer):
name = serializers.CharField()
content = serializers.CharField()
class UserSerializer(serializers.Serializer):
username = serializers.CharField()
notes = NoteSerializer(required=False, many=True)
At first I create a user using UserSerializer(partial=False) and set only one reqiured field username. Then I only edit that user, so I use UserSerializer(partial=True). But if I want to add notes for that user sending a data to UserSerializer: {"notes": [{...},{...}]} all NoteSerializer fields become not required. It is because NoteSerializer inherited partial=True from UserSerializer.
It would be good to have possibility to redefine partial for nested serializer:
...
notes = NoteSerializer(required=False, many=True, partial=False)
I think partial should have 3 states for nested serializers: True, False, Inherited.
I'm against having 3 states for partials. This sounds like the initial issue we had with writable nested where there will be too many different use cases.
Note that you could use a note entry point to benefit from the full validation.
On my opinion inheritance of partial has not much usecases. Fields should check only partial flag of parent serializer instead of root serializer. It would give more flexibility in usage of partial. With such approach inheritance may be even not implemented in DRF. DRF user will be able to easy implement inheritance by himself if needed.
This is biting me too. I have implemented nested writes in one of my serializers by having the nested instances created from scratch each time:
def create(self, validated_data):
choices = validated_data.pop('choices')
with transaction.atomic():
product = super().create(validated_data)
for choice in choices:
product.choices.create(**choice)
return product
The problem occurs when a partial update is applied. The partial=True parameter is being passed to the nested class, allowing fields to be completely omitted. It would be nice if this behavior could be changed by passing an option to the nested serializer.
I found a workaround in the meantime. It's ugly but it works. The key to the patch is a context manager class named MonkeyPatchPartial. Its job is to "patch" the value of partial in the root serializer:
class MonkeyPatchPartial:
"""
Work around bug #3847 in djangorestframework by monkey-patching the partial
attribute of the root serializer during the call to validate_empty_values.
"""
def __init__(self, root):
self._root = root
def __enter__(self):
self._old = getattr(self._root, 'partial')
setattr(self._root, 'partial', False)
def __exit__(self, *args):
setattr(self._root, 'partial', self._old)
Now, all you need to do is instantiate the class in the nested serializer's run_validation() method:
def run_validation(self, *args, **kwargs):
with MonkeyPatchPartial(self.root):
return super().run_validation(*args, **kwargs)
This will keep the value of partial set to False long enough for each field to be properly validated.
Been giving this a second though.
I'm going to reject this request, I don't want to open a door to a long list of specific use cases to be handled since there's an easy solution.
I would advice to void the partial flag within the nested serializer by overriding the nested serializer's __init__.
I don't understand how it is possible to override the value in the nested serializer's __init__ method. This, for example, doesn't do anything:
def __init__(self, *args, **kwargs):
super().__init__(*args, partial=False, **kwargs)
My ugly hack is the only way I've been able to get this to work at all since the nested serializer queries the value of self.root.partial in order to determine whether to perform a partial update. It doesn't matter what's in the nested serializer.
Please reconsider this issue. There is no "easy solution".
I've overcome this with overriding bind method in nested serializer:
def bind(self, field_name, parent):
super(NestedSerializer, self).bind(field_name, parent)
self.parent = None
so self.root is self for nested serializer.
I agree it could open up a birds nest of stuff. However, it feels like expected behavior when you put partial=false to a NestedSerializer.
I think I can come up with a solution here.
Indeed, overriding the init will not be enough because of root and altering root will have nasty side effects in particular for hyperlinks.
It's easy to create some mixin with patch that @nathan-osman introduced.
class OverrideRootPartialMixin:
def run_validation(self, *args, **kwargs):
if not self.partial:
with MonkeyPatchPartial(self.root):
return super().run_validation(*args, **kwargs)
return super().run_validation(*args, **kwargs)
So your serializers should be:
class ChildSerializer(OverrideRootPartialMixin, serializers.Serializer):
fields = ...
class ParentSerializer(serializers.Serializer):
child_field = ChildSerializer(partial=False)
And setting (partial=False) would keep an eye on required param of fields for child serializers.
I am also in need of doing this. I have one field that is required when the serializer is POSTed to directly, however, when the serializer is POSTed to as a nested serializer the field is not needed. Setting partial=True in the nested situation should have done the trick, but alas it did not work. I used a combination of @nathan-osman and @vishes-shell ideas, however, I needed to reverse the logic.
@xordoquy any updates on this? The issue is closed without any real solution to the problem (considering your words that tweaking the root is dangerous).
Hello. May I bring this up again after a quarter? Has anyone found a less hacky solution, which can be applied to DRF 3.7.x?
Can not really remember what I was thinking about and I must say my advice those days is that nested writable serializers should be avoided.
No obvious behavior is to be expected client side which means it is overall a bad thing.
At first sight, I still think instantiating the nested fields within the root's __init__ should do.
Thank you, that's a very valid point.
Easiest hack around this issue seems to be overriding the "root" and setting its partial attribute to False.
Most helpful comment
I found a workaround in the meantime. It's ugly but it works. The key to the patch is a context manager class named
MonkeyPatchPartial. Its job is to "patch" the value ofpartialin the root serializer:Now, all you need to do is instantiate the class in the nested serializer's
run_validation()method:This will keep the value of
partialset toFalselong enough for each field to be properly validated.