Make set_value a method within Serializer (#8001)

* Make set_value a static method for Serializers

As an alternative to #7671, let the method be overridden if needed. As
the function is only used for serializers, it has a better place in the
Serializer class.

* Set `set_value` as an object (non-static) method

* Add tests for set_value()

These tests follow the examples given in the method.
This commit is contained in:
Étienne Beaulé 2023-05-24 10:59:42 -03:00 committed by GitHub
parent a25aac7d67
commit d252d22343
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 23 deletions

View File

@ -113,27 +113,6 @@ def get_attribute(instance, attrs):
return instance return instance
def set_value(dictionary, keys, value):
"""
Similar to Python's built in `dictionary[key] = value`,
but takes a list of nested keys instead of a single key.
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
"""
if not keys:
dictionary.update(value)
return
for key in keys[:-1]:
if key not in dictionary:
dictionary[key] = {}
dictionary = dictionary[key]
dictionary[keys[-1]] = value
def to_choices_dict(choices): def to_choices_dict(choices):
""" """
Convert choices into key/value dicts. Convert choices into key/value dicts.

View File

@ -28,7 +28,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.compat import postgres_fields from rest_framework.compat import postgres_fields
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import get_error_detail, set_value from rest_framework.fields import get_error_detail
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation from rest_framework.utils import html, model_meta, representation
from rest_framework.utils.field_mapping import ( from rest_framework.utils.field_mapping import (
@ -346,6 +346,26 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
} }
def set_value(self, dictionary, keys, value):
"""
Similar to Python's built in `dictionary[key] = value`,
but takes a list of nested keys instead of a single key.
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
"""
if not keys:
dictionary.update(value)
return
for key in keys[:-1]:
if key not in dictionary:
dictionary[key] = {}
dictionary = dictionary[key]
dictionary[keys[-1]] = value
@cached_property @cached_property
def fields(self): def fields(self):
""" """
@ -492,7 +512,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
except SkipField: except SkipField:
pass pass
else: else:
set_value(ret, field.source_attrs, validated_value) self.set_value(ret, field.source_attrs, validated_value)
if errors: if errors:
raise ValidationError(errors) raise ValidationError(errors)

View File

@ -762,3 +762,24 @@ class Test8301Regression:
assert (s.data | {}).__class__ == s.data.__class__ assert (s.data | {}).__class__ == s.data.__class__
assert ({} | s.data).__class__ == s.data.__class__ assert ({} | s.data).__class__ == s.data.__class__
class TestSetValueMethod:
# Serializer.set_value() modifies the first parameter in-place.
s = serializers.Serializer()
def test_no_keys(self):
ret = {'a': 1}
self.s.set_value(ret, [], {'b': 2})
assert ret == {'a': 1, 'b': 2}
def test_one_key(self):
ret = {'a': 1}
self.s.set_value(ret, ['x'], 2)
assert ret == {'a': 1, 'x': 2}
def test_nested_key(self):
ret = {'a': 1}
self.s.set_value(ret, ['x', 'y'], 2)
assert ret == {'a': 1, 'x': {'y': 2}}