expose configurable allowing a default to be set on partial updates

This commit is contained in:
Max Morlocke 2020-12-25 21:40:28 -05:00
parent 8351747d98
commit 57968137fc
3 changed files with 29 additions and 5 deletions

View File

@ -48,7 +48,7 @@ Defaults to `True`.
If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behaviour is to not populate the attribute at all. If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behaviour is to not populate the attribute at all.
The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned. The `default` is not applied during partial update operations, unless `set_default_if_partial` is set to True. In the typical path partial update case only fields that are provided in the incoming data will have a validated value returned.
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `requires_context = True` attribute, then the serializer field will be passed as an argument. May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `requires_context = True` attribute, then the serializer field will be passed as an argument.
@ -68,6 +68,12 @@ When serializing the instance, default will be used if the object attribute or d
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error. Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.
### set_default_if_partial
The `default` is not applied during partial update operations, unless `set_default_if_partial` is set to True. If set to True, the partial update will include the default value. Otherwise, partial updates will only apply to fields provided in the incoming data.
Note: This parameter is particularly useful for HiddenInput's with default values.
### `allow_null` ### `allow_null`
Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value. Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value.
@ -542,7 +548,7 @@ For example, if `has_expired` was a property on the `Account` model, then the fo
## HiddenField ## HiddenField
A field class that does not take a value based on user input, but instead takes its value from a default value or callable. A field class that does not take a value based on user input, but instead takes its value from a default value or callable. In the case of a partial update, the `default` value will not be applied unless `set_default_on_partial` is set to True for this field.
**Signature**: `HiddenField()` **Signature**: `HiddenField()`

View File

@ -307,6 +307,7 @@ MISSING_ERROR_MESSAGE = (
'ValidationError raised by `{class_name}`, but error key `{key}` does ' 'ValidationError raised by `{class_name}`, but error key `{key}` does '
'not exist in the `error_messages` dictionary.' 'not exist in the `error_messages` dictionary.'
) )
NO_DEFAULT_SET_ON_PARTIAL = 'May not enforce set_default_on_partial without setting a default value'
class Field: class Field:
@ -323,7 +324,7 @@ class Field:
def __init__(self, read_only=False, write_only=False, def __init__(self, read_only=False, write_only=False,
required=None, default=empty, initial=empty, source=None, required=None, default=empty, initial=empty, source=None,
label=None, help_text=None, style=None, label=None, help_text=None, style=None,
error_messages=None, validators=None, allow_null=False): error_messages=None, validators=None, allow_null=False, set_default_on_partial=False):
self._creation_counter = Field._creation_counter self._creation_counter = Field._creation_counter
Field._creation_counter += 1 Field._creation_counter += 1
@ -336,6 +337,7 @@ class Field:
assert not (read_only and required), NOT_READ_ONLY_REQUIRED assert not (read_only and required), NOT_READ_ONLY_REQUIRED
assert not (required and default is not empty), NOT_REQUIRED_DEFAULT assert not (required and default is not empty), NOT_REQUIRED_DEFAULT
assert not (read_only and self.__class__ == Field), USE_READONLYFIELD assert not (read_only and self.__class__ == Field), USE_READONLYFIELD
assert not (default is empty and set_default_on_partial), NO_DEFAULT_SET_ON_PARTIAL
self.read_only = read_only self.read_only = read_only
self.write_only = write_only self.write_only = write_only
@ -347,6 +349,7 @@ class Field:
self.help_text = help_text self.help_text = help_text
self.style = {} if style is None else style self.style = {} if style is None else style
self.allow_null = allow_null self.allow_null = allow_null
self.set_default_on_partial = set_default_on_partial
if self.default_empty_html is not empty: if self.default_empty_html is not empty:
if default is not empty: if default is not empty:
@ -498,8 +501,11 @@ class Field:
raise `SkipField`, indicating that no value should be set in the raise `SkipField`, indicating that no value should be set in the
validated data for this field. validated data for this field.
""" """
if self.default is empty or getattr(self.root, 'partial', False): if self.default is empty or all([
# No default, or this is a partial update. getattr(self.root, 'partial', False),
not getattr(self.root, "set_default_on_partial", False)
]):
# No default, or this is a partial update where defaults are not set.
raise SkipField() raise SkipField()
if callable(self.default): if callable(self.default):
if hasattr(self.default, 'set_context'): if hasattr(self.default, 'set_context'):

View File

@ -610,6 +610,18 @@ class TestDefaultInclusions:
assert serializer.validated_data == {'integer': 456} assert serializer.validated_data == {'integer': 456}
assert serializer.errors == {} assert serializer.errors == {}
def test_default_should_be_included_on_partial_update_when_set_default_on_partial_true(self):
class ExampleSerializer(serializers.Serializer):
char = serializers.CharField(default='abc', set_default_on_partial=True)
integer = serializers.IntegerField()
float = serializers.FloatField(required=False)
instance = MockObject(char='def', integer=123, float=4.2)
serializer = ExampleSerializer(instance, data={'integer': 456})
assert serializer.is_valid()
assert serializer.validated_data == {'char': 'abc', 'integer': 456}
assert serializer.errors == {}
class TestSerializerValidationWithCompiledRegexField: class TestSerializerValidationWithCompiledRegexField:
def setup(self): def setup(self):