mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-14 05:36:50 +03:00
Lazy loading of fields and validators. Closes #1963.
This commit is contained in:
parent
11075d3770
commit
207208fedf
|
@ -143,7 +143,7 @@ class Field(object):
|
||||||
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=[], allow_null=False):
|
error_messages=None, validators=None, allow_null=False):
|
||||||
self._creation_counter = Field._creation_counter
|
self._creation_counter = Field._creation_counter
|
||||||
Field._creation_counter += 1
|
Field._creation_counter += 1
|
||||||
|
|
||||||
|
@ -166,9 +166,11 @@ class Field(object):
|
||||||
self.label = label
|
self.label = label
|
||||||
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.validators = validators[:] or self.default_validators[:]
|
|
||||||
self.allow_null = allow_null
|
self.allow_null = allow_null
|
||||||
|
|
||||||
|
if validators is not None:
|
||||||
|
self.validators = validators[:]
|
||||||
|
|
||||||
# These are set up by `.bind()` when the field is added to a serializer.
|
# These are set up by `.bind()` when the field is added to a serializer.
|
||||||
self.field_name = None
|
self.field_name = None
|
||||||
self.parent = None
|
self.parent = None
|
||||||
|
@ -214,6 +216,21 @@ class Field(object):
|
||||||
else:
|
else:
|
||||||
self.source_attrs = self.source.split('.')
|
self.source_attrs = self.source.split('.')
|
||||||
|
|
||||||
|
# .validators is a lazily loaded property, that gets its default
|
||||||
|
# value from `get_validators`.
|
||||||
|
@property
|
||||||
|
def validators(self):
|
||||||
|
if not hasattr(self, '_validators'):
|
||||||
|
self._validators = self.get_validators()
|
||||||
|
return self._validators
|
||||||
|
|
||||||
|
@validators.setter
|
||||||
|
def validators(self, validators):
|
||||||
|
self._validators = validators
|
||||||
|
|
||||||
|
def get_validators(self):
|
||||||
|
return self.default_validators[:]
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""
|
"""
|
||||||
Return a value to use when the field is being returned as a primitive
|
Return a value to use when the field is being returned as a primitive
|
||||||
|
|
|
@ -282,21 +282,23 @@ class SerializerMetaclass(type):
|
||||||
|
|
||||||
@six.add_metaclass(SerializerMetaclass)
|
@six.add_metaclass(SerializerMetaclass)
|
||||||
class Serializer(BaseSerializer):
|
class Serializer(BaseSerializer):
|
||||||
def __init__(self, *args, **kwargs):
|
@property
|
||||||
super(Serializer, self).__init__(*args, **kwargs)
|
def fields(self):
|
||||||
|
if not hasattr(self, '_fields'):
|
||||||
|
self._fields = BindingDict(self)
|
||||||
|
for key, value in self.get_fields().items():
|
||||||
|
self._fields[key] = value
|
||||||
|
return self._fields
|
||||||
|
|
||||||
|
def get_fields(self):
|
||||||
# Every new serializer is created with a clone of the field instances.
|
# Every new serializer is created with a clone of the field instances.
|
||||||
# This allows users to dynamically modify the fields on a serializer
|
# This allows users to dynamically modify the fields on a serializer
|
||||||
# instance without affecting every other serializer class.
|
# instance without affecting every other serializer class.
|
||||||
self.fields = BindingDict(self)
|
|
||||||
for key, value in self._get_base_fields().items():
|
|
||||||
self.fields[key] = value
|
|
||||||
|
|
||||||
self.validators = getattr(getattr(self, 'Meta', None), 'validators', [])
|
|
||||||
|
|
||||||
def _get_base_fields(self):
|
|
||||||
return copy.deepcopy(self._declared_fields)
|
return copy.deepcopy(self._declared_fields)
|
||||||
|
|
||||||
|
def get_validators(self):
|
||||||
|
return getattr(getattr(self, 'Meta', None), 'validators', [])
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
if self._initial_data is not None:
|
if self._initial_data is not None:
|
||||||
return ReturnDict([
|
return ReturnDict([
|
||||||
|
@ -520,14 +522,6 @@ class ModelSerializer(Serializer):
|
||||||
})
|
})
|
||||||
_related_class = PrimaryKeyRelatedField
|
_related_class = PrimaryKeyRelatedField
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ModelSerializer, self).__init__(*args, **kwargs)
|
|
||||||
if 'validators' not in kwargs:
|
|
||||||
validators = self.get_default_validators()
|
|
||||||
if validators:
|
|
||||||
self.validators.extend(validators)
|
|
||||||
self._kwargs['validators'] = validators
|
|
||||||
|
|
||||||
def create(self, validated_attrs):
|
def create(self, validated_attrs):
|
||||||
# Check that the user isn't trying to handle a writable nested field.
|
# Check that the user isn't trying to handle a writable nested field.
|
||||||
# If we don't do this explicitly they'd likely get a confusing
|
# If we don't do this explicitly they'd likely get a confusing
|
||||||
|
@ -578,13 +572,13 @@ class ModelSerializer(Serializer):
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def get_default_validators(self):
|
def get_validators(self):
|
||||||
field_names = set([
|
field_names = set([
|
||||||
field.source for field in self.fields.values()
|
field.source for field in self.fields.values()
|
||||||
if (field.source != '*') and ('.' not in field.source)
|
if (field.source != '*') and ('.' not in field.source)
|
||||||
])
|
])
|
||||||
|
|
||||||
validators = []
|
validators = getattr(getattr(self, 'Meta', None), 'validators', [])
|
||||||
model_class = self.Meta.model
|
model_class = self.Meta.model
|
||||||
|
|
||||||
# Note that we make sure to check `unique_together` both on the
|
# Note that we make sure to check `unique_together` both on the
|
||||||
|
@ -627,7 +621,7 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
|
|
||||||
def _get_base_fields(self):
|
def get_fields(self):
|
||||||
declared_fields = copy.deepcopy(self._declared_fields)
|
declared_fields = copy.deepcopy(self._declared_fields)
|
||||||
|
|
||||||
ret = SortedDict()
|
ret = SortedDict()
|
||||||
|
|
|
@ -82,6 +82,11 @@ def serializer_repr(serializer, indent, force_many=None):
|
||||||
ret += field_repr(field.child_relation, force_many=field.child_relation)
|
ret += field_repr(field.child_relation, force_many=field.child_relation)
|
||||||
else:
|
else:
|
||||||
ret += field_repr(field)
|
ret += field_repr(field)
|
||||||
|
|
||||||
|
if serializer.validators:
|
||||||
|
ret += '\n' + indent_str + 'class Meta:'
|
||||||
|
ret += '\n' + indent_str + ' validators = ' + smart_repr(serializer.validators)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ class TestSource:
|
||||||
class ExampleSerializer(serializers.Serializer):
|
class ExampleSerializer(serializers.Serializer):
|
||||||
example_field = serializers.CharField(source='example_field')
|
example_field = serializers.CharField(source='example_field')
|
||||||
with pytest.raises(AssertionError) as exc_info:
|
with pytest.raises(AssertionError) as exc_info:
|
||||||
ExampleSerializer()
|
ExampleSerializer().fields
|
||||||
assert str(exc_info.value) == (
|
assert str(exc_info.value) == (
|
||||||
"It is redundant to specify `source='example_field'` on field "
|
"It is redundant to specify `source='example_field'` on field "
|
||||||
"'CharField' in serializer 'ExampleSerializer', because it is the "
|
"'CharField' in serializer 'ExampleSerializer', because it is the "
|
||||||
|
@ -1018,7 +1018,7 @@ class TestSerializerMethodField:
|
||||||
example_field = serializers.SerializerMethodField('get_example_field')
|
example_field = serializers.SerializerMethodField('get_example_field')
|
||||||
|
|
||||||
with pytest.raises(AssertionError) as exc_info:
|
with pytest.raises(AssertionError) as exc_info:
|
||||||
ExampleSerializer()
|
ExampleSerializer().fields
|
||||||
assert str(exc_info.value) == (
|
assert str(exc_info.value) == (
|
||||||
"It is redundant to specify `get_example_field` on "
|
"It is redundant to specify `get_example_field` on "
|
||||||
"SerializerMethodField 'example_field' in serializer "
|
"SerializerMethodField 'example_field' in serializer "
|
||||||
|
|
|
@ -181,7 +181,7 @@ class TestRegularFieldMappings(TestCase):
|
||||||
fields = ('auto_field', 'invalid')
|
fields = ('auto_field', 'invalid')
|
||||||
|
|
||||||
with self.assertRaises(ImproperlyConfigured) as excinfo:
|
with self.assertRaises(ImproperlyConfigured) as excinfo:
|
||||||
TestSerializer()
|
TestSerializer().fields
|
||||||
expected = 'Field name `invalid` is not valid for model `ModelBase`.'
|
expected = 'Field name `invalid` is not valid for model `ModelBase`.'
|
||||||
assert str(excinfo.exception) == expected
|
assert str(excinfo.exception) == expected
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ class TestRegularFieldMappings(TestCase):
|
||||||
fields = ('auto_field',)
|
fields = ('auto_field',)
|
||||||
|
|
||||||
with self.assertRaises(ImproperlyConfigured) as excinfo:
|
with self.assertRaises(ImproperlyConfigured) as excinfo:
|
||||||
TestSerializer()
|
TestSerializer().fields
|
||||||
expected = (
|
expected = (
|
||||||
'Field `missing` has been declared on serializer '
|
'Field `missing` has been declared on serializer '
|
||||||
'`TestSerializer`, but is missing from `Meta.fields`.'
|
'`TestSerializer`, but is missing from `Meta.fields`.'
|
||||||
|
|
|
@ -86,10 +86,12 @@ class TestUniquenessTogetherValidation(TestCase):
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
serializer = UniquenessTogetherSerializer()
|
serializer = UniquenessTogetherSerializer()
|
||||||
expected = dedent("""
|
expected = dedent("""
|
||||||
UniquenessTogetherSerializer(validators=[<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>]):
|
UniquenessTogetherSerializer():
|
||||||
id = IntegerField(label='ID', read_only=True)
|
id = IntegerField(label='ID', read_only=True)
|
||||||
race_name = CharField(max_length=100)
|
race_name = CharField(max_length=100)
|
||||||
position = IntegerField()
|
position = IntegerField()
|
||||||
|
class Meta:
|
||||||
|
validators = [<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>]
|
||||||
""")
|
""")
|
||||||
assert repr(serializer) == expected
|
assert repr(serializer) == expected
|
||||||
|
|
||||||
|
@ -173,10 +175,12 @@ class TestUniquenessForDateValidation(TestCase):
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
serializer = UniqueForDateSerializer()
|
serializer = UniqueForDateSerializer()
|
||||||
expected = dedent("""
|
expected = dedent("""
|
||||||
UniqueForDateSerializer(validators=[<UniqueForDateValidator(queryset=UniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
|
UniqueForDateSerializer():
|
||||||
id = IntegerField(label='ID', read_only=True)
|
id = IntegerField(label='ID', read_only=True)
|
||||||
slug = CharField(max_length=100)
|
slug = CharField(max_length=100)
|
||||||
published = DateField(required=True)
|
published = DateField(required=True)
|
||||||
|
class Meta:
|
||||||
|
validators = [<UniqueForDateValidator(queryset=UniqueForDateModel.objects.all(), field='slug', date_field='published')>]
|
||||||
""")
|
""")
|
||||||
assert repr(serializer) == expected
|
assert repr(serializer) == expected
|
||||||
|
|
||||||
|
@ -231,10 +235,12 @@ class TestHiddenFieldUniquenessForDateValidation(TestCase):
|
||||||
|
|
||||||
serializer = TestSerializer()
|
serializer = TestSerializer()
|
||||||
expected = dedent("""
|
expected = dedent("""
|
||||||
TestSerializer(validators=[<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
|
TestSerializer():
|
||||||
id = IntegerField(label='ID', read_only=True)
|
id = IntegerField(label='ID', read_only=True)
|
||||||
slug = CharField(max_length=100)
|
slug = CharField(max_length=100)
|
||||||
published = HiddenField(default=CreateOnlyDefault(<function now>))
|
published = HiddenField(default=CreateOnlyDefault(<function now>))
|
||||||
|
class Meta:
|
||||||
|
validators = [<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]
|
||||||
""")
|
""")
|
||||||
assert repr(serializer) == expected
|
assert repr(serializer) == expected
|
||||||
|
|
||||||
|
@ -246,9 +252,11 @@ class TestHiddenFieldUniquenessForDateValidation(TestCase):
|
||||||
|
|
||||||
serializer = TestSerializer()
|
serializer = TestSerializer()
|
||||||
expected = dedent("""
|
expected = dedent("""
|
||||||
TestSerializer(validators=[<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
|
TestSerializer():
|
||||||
id = IntegerField(label='ID', read_only=True)
|
id = IntegerField(label='ID', read_only=True)
|
||||||
slug = CharField(max_length=100)
|
slug = CharField(max_length=100)
|
||||||
published = DateTimeField(default=CreateOnlyDefault(<function now>), read_only=True)
|
published = DateTimeField(default=CreateOnlyDefault(<function now>), read_only=True)
|
||||||
|
class Meta:
|
||||||
|
validators = [<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]
|
||||||
""")
|
""")
|
||||||
assert repr(serializer) == expected
|
assert repr(serializer) == expected
|
||||||
|
|
Loading…
Reference in New Issue
Block a user