From 207208fedff2457e921ef7d825ea7c3933b5dd6e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 31 Oct 2014 16:38:39 +0000 Subject: [PATCH] Lazy loading of fields and validators. Closes #1963. --- rest_framework/fields.py | 21 ++++++++++++++-- rest_framework/serializers.py | 34 +++++++++++--------------- rest_framework/utils/representation.py | 5 ++++ tests/test_fields.py | 4 +-- tests/test_model_serializer.py | 4 +-- tests/test_validators.py | 16 +++++++++--- 6 files changed, 54 insertions(+), 30 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d5dccd723..8cc8e81ec 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -143,7 +143,7 @@ class Field(object): def __init__(self, read_only=False, write_only=False, required=None, default=empty, initial=empty, source=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 Field._creation_counter += 1 @@ -166,9 +166,11 @@ class Field(object): self.label = label self.help_text = help_text self.style = {} if style is None else style - self.validators = validators[:] or self.default_validators[:] 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. self.field_name = None self.parent = None @@ -214,6 +216,21 @@ class Field(object): else: 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): """ Return a value to use when the field is being returned as a primitive diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c9f70f2da..82e932ddd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -282,21 +282,23 @@ class SerializerMetaclass(type): @six.add_metaclass(SerializerMetaclass) class Serializer(BaseSerializer): - def __init__(self, *args, **kwargs): - super(Serializer, self).__init__(*args, **kwargs) + @property + 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. # This allows users to dynamically modify the fields on a serializer # 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) + def get_validators(self): + return getattr(getattr(self, 'Meta', None), 'validators', []) + def get_initial(self): if self._initial_data is not None: return ReturnDict([ @@ -520,14 +522,6 @@ class ModelSerializer(Serializer): }) _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): # 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 @@ -578,13 +572,13 @@ class ModelSerializer(Serializer): instance.save() return instance - def get_default_validators(self): + def get_validators(self): field_names = set([ field.source for field in self.fields.values() if (field.source != '*') and ('.' not in field.source) ]) - validators = [] + validators = getattr(getattr(self, 'Meta', None), 'validators', []) model_class = self.Meta.model # Note that we make sure to check `unique_together` both on the @@ -627,7 +621,7 @@ class ModelSerializer(Serializer): return validators - def _get_base_fields(self): + def get_fields(self): declared_fields = copy.deepcopy(self._declared_fields) ret = SortedDict() diff --git a/rest_framework/utils/representation.py b/rest_framework/utils/representation.py index 180b51f8d..2a7c46753 100644 --- a/rest_framework/utils/representation.py +++ b/rest_framework/utils/representation.py @@ -82,6 +82,11 @@ def serializer_repr(serializer, indent, force_many=None): ret += field_repr(field.child_relation, force_many=field.child_relation) else: ret += field_repr(field) + + if serializer.validators: + ret += '\n' + indent_str + 'class Meta:' + ret += '\n' + indent_str + ' validators = ' + smart_repr(serializer.validators) + return ret diff --git a/tests/test_fields.py b/tests/test_fields.py index 3e102ab5a..96d09900e 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -85,7 +85,7 @@ class TestSource: class ExampleSerializer(serializers.Serializer): example_field = serializers.CharField(source='example_field') with pytest.raises(AssertionError) as exc_info: - ExampleSerializer() + ExampleSerializer().fields assert str(exc_info.value) == ( "It is redundant to specify `source='example_field'` on field " "'CharField' in serializer 'ExampleSerializer', because it is the " @@ -1018,7 +1018,7 @@ class TestSerializerMethodField: example_field = serializers.SerializerMethodField('get_example_field') with pytest.raises(AssertionError) as exc_info: - ExampleSerializer() + ExampleSerializer().fields assert str(exc_info.value) == ( "It is redundant to specify `get_example_field` on " "SerializerMethodField 'example_field' in serializer " diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 18170bc06..b8b621be8 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -181,7 +181,7 @@ class TestRegularFieldMappings(TestCase): fields = ('auto_field', 'invalid') with self.assertRaises(ImproperlyConfigured) as excinfo: - TestSerializer() + TestSerializer().fields expected = 'Field name `invalid` is not valid for model `ModelBase`.' assert str(excinfo.exception) == expected @@ -198,7 +198,7 @@ class TestRegularFieldMappings(TestCase): fields = ('auto_field',) with self.assertRaises(ImproperlyConfigured) as excinfo: - TestSerializer() + TestSerializer().fields expected = ( 'Field `missing` has been declared on serializer ' '`TestSerializer`, but is missing from `Meta.fields`.' diff --git a/tests/test_validators.py b/tests/test_validators.py index 6cc52c837..e6e0b23a8 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -86,10 +86,12 @@ class TestUniquenessTogetherValidation(TestCase): def test_repr(self): serializer = UniquenessTogetherSerializer() expected = dedent(""" - UniquenessTogetherSerializer(validators=[]): + UniquenessTogetherSerializer(): id = IntegerField(label='ID', read_only=True) race_name = CharField(max_length=100) position = IntegerField() + class Meta: + validators = [] """) assert repr(serializer) == expected @@ -173,10 +175,12 @@ class TestUniquenessForDateValidation(TestCase): def test_repr(self): serializer = UniqueForDateSerializer() expected = dedent(""" - UniqueForDateSerializer(validators=[]): + UniqueForDateSerializer(): id = IntegerField(label='ID', read_only=True) slug = CharField(max_length=100) published = DateField(required=True) + class Meta: + validators = [] """) assert repr(serializer) == expected @@ -231,10 +235,12 @@ class TestHiddenFieldUniquenessForDateValidation(TestCase): serializer = TestSerializer() expected = dedent(""" - TestSerializer(validators=[]): + TestSerializer(): id = IntegerField(label='ID', read_only=True) slug = CharField(max_length=100) published = HiddenField(default=CreateOnlyDefault()) + class Meta: + validators = [] """) assert repr(serializer) == expected @@ -246,9 +252,11 @@ class TestHiddenFieldUniquenessForDateValidation(TestCase): serializer = TestSerializer() expected = dedent(""" - TestSerializer(validators=[]): + TestSerializer(): id = IntegerField(label='ID', read_only=True) slug = CharField(max_length=100) published = DateTimeField(default=CreateOnlyDefault(), read_only=True) + class Meta: + validators = [] """) assert repr(serializer) == expected