Skip to content

Commit 207208f

Browse files
committed
Lazy loading of fields and validators. Closes #1963.
1 parent 11075d3 commit 207208f

File tree

6 files changed

+55
-31
lines changed

6 files changed

+55
-31
lines changed

rest_framework/fields.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class Field(object):
143143
def __init__(self, read_only=False, write_only=False,
144144
required=None, default=empty, initial=empty, source=None,
145145
label=None, help_text=None, style=None,
146-
error_messages=None, validators=[], allow_null=False):
146+
error_messages=None, validators=None, allow_null=False):
147147
self._creation_counter = Field._creation_counter
148148
Field._creation_counter += 1
149149

@@ -166,9 +166,11 @@ def __init__(self, read_only=False, write_only=False,
166166
self.label = label
167167
self.help_text = help_text
168168
self.style = {} if style is None else style
169-
self.validators = validators[:] or self.default_validators[:]
170169
self.allow_null = allow_null
171170

171+
if validators is not None:
172+
self.validators = validators[:]
173+
172174
# These are set up by `.bind()` when the field is added to a serializer.
173175
self.field_name = None
174176
self.parent = None
@@ -214,6 +216,21 @@ def bind(self, field_name, parent):
214216
else:
215217
self.source_attrs = self.source.split('.')
216218

219+
# .validators is a lazily loaded property, that gets its default
220+
# value from `get_validators`.
221+
@property
222+
def validators(self):
223+
if not hasattr(self, '_validators'):
224+
self._validators = self.get_validators()
225+
return self._validators
226+
227+
@validators.setter
228+
def validators(self, validators):
229+
self._validators = validators
230+
231+
def get_validators(self):
232+
return self.default_validators[:]
233+
217234
def get_initial(self):
218235
"""
219236
Return a value to use when the field is being returned as a primitive

rest_framework/serializers.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -282,21 +282,23 @@ def __new__(cls, name, bases, attrs):
282282

283283
@six.add_metaclass(SerializerMetaclass)
284284
class Serializer(BaseSerializer):
285-
def __init__(self, *args, **kwargs):
286-
super(Serializer, self).__init__(*args, **kwargs)
287-
285+
@property
286+
def fields(self):
287+
if not hasattr(self, '_fields'):
288+
self._fields = BindingDict(self)
289+
for key, value in self.get_fields().items():
290+
self._fields[key] = value
291+
return self._fields
292+
293+
def get_fields(self):
288294
# Every new serializer is created with a clone of the field instances.
289295
# This allows users to dynamically modify the fields on a serializer
290296
# instance without affecting every other serializer class.
291-
self.fields = BindingDict(self)
292-
for key, value in self._get_base_fields().items():
293-
self.fields[key] = value
294-
295-
self.validators = getattr(getattr(self, 'Meta', None), 'validators', [])
296-
297-
def _get_base_fields(self):
298297
return copy.deepcopy(self._declared_fields)
299298

299+
def get_validators(self):
300+
return getattr(getattr(self, 'Meta', None), 'validators', [])
301+
300302
def get_initial(self):
301303
if self._initial_data is not None:
302304
return ReturnDict([
@@ -520,14 +522,6 @@ class ModelSerializer(Serializer):
520522
})
521523
_related_class = PrimaryKeyRelatedField
522524

523-
def __init__(self, *args, **kwargs):
524-
super(ModelSerializer, self).__init__(*args, **kwargs)
525-
if 'validators' not in kwargs:
526-
validators = self.get_default_validators()
527-
if validators:
528-
self.validators.extend(validators)
529-
self._kwargs['validators'] = validators
530-
531525
def create(self, validated_attrs):
532526
# Check that the user isn't trying to handle a writable nested field.
533527
# If we don't do this explicitly they'd likely get a confusing
@@ -578,13 +572,13 @@ def update(self, instance, validated_attrs):
578572
instance.save()
579573
return instance
580574

581-
def get_default_validators(self):
575+
def get_validators(self):
582576
field_names = set([
583577
field.source for field in self.fields.values()
584578
if (field.source != '*') and ('.' not in field.source)
585579
])
586580

587-
validators = []
581+
validators = getattr(getattr(self, 'Meta', None), 'validators', [])
588582
model_class = self.Meta.model
589583

590584
# Note that we make sure to check `unique_together` both on the
@@ -627,7 +621,7 @@ def get_default_validators(self):
627621

628622
return validators
629623

630-
def _get_base_fields(self):
624+
def get_fields(self):
631625
declared_fields = copy.deepcopy(self._declared_fields)
632626

633627
ret = SortedDict()

rest_framework/utils/representation.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ def serializer_repr(serializer, indent, force_many=None):
8282
ret += field_repr(field.child_relation, force_many=field.child_relation)
8383
else:
8484
ret += field_repr(field)
85+
86+
if serializer.validators:
87+
ret += '\n' + indent_str + 'class Meta:'
88+
ret += '\n' + indent_str + ' validators = ' + smart_repr(serializer.validators)
89+
8590
return ret
8691

8792

tests/test_fields.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test_redundant_source(self):
8585
class ExampleSerializer(serializers.Serializer):
8686
example_field = serializers.CharField(source='example_field')
8787
with pytest.raises(AssertionError) as exc_info:
88-
ExampleSerializer()
88+
ExampleSerializer().fields
8989
assert str(exc_info.value) == (
9090
"It is redundant to specify `source='example_field'` on field "
9191
"'CharField' in serializer 'ExampleSerializer', because it is the "
@@ -1018,7 +1018,7 @@ class ExampleSerializer(serializers.Serializer):
10181018
example_field = serializers.SerializerMethodField('get_example_field')
10191019

10201020
with pytest.raises(AssertionError) as exc_info:
1021-
ExampleSerializer()
1021+
ExampleSerializer().fields
10221022
assert str(exc_info.value) == (
10231023
"It is redundant to specify `get_example_field` on "
10241024
"SerializerMethodField 'example_field' in serializer "

tests/test_model_serializer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class Meta:
181181
fields = ('auto_field', 'invalid')
182182

183183
with self.assertRaises(ImproperlyConfigured) as excinfo:
184-
TestSerializer()
184+
TestSerializer().fields
185185
expected = 'Field name `invalid` is not valid for model `ModelBase`.'
186186
assert str(excinfo.exception) == expected
187187

@@ -198,7 +198,7 @@ class Meta:
198198
fields = ('auto_field',)
199199

200200
with self.assertRaises(ImproperlyConfigured) as excinfo:
201-
TestSerializer()
201+
TestSerializer().fields
202202
expected = (
203203
'Field `missing` has been declared on serializer '
204204
'`TestSerializer`, but is missing from `Meta.fields`.'

tests/test_validators.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,12 @@ def setUp(self):
8686
def test_repr(self):
8787
serializer = UniquenessTogetherSerializer()
8888
expected = dedent("""
89-
UniquenessTogetherSerializer(validators=[<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>]):
89+
UniquenessTogetherSerializer():
9090
id = IntegerField(label='ID', read_only=True)
9191
race_name = CharField(max_length=100)
9292
position = IntegerField()
93+
class Meta:
94+
validators = [<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>]
9395
""")
9496
assert repr(serializer) == expected
9597

@@ -173,10 +175,12 @@ def setUp(self):
173175
def test_repr(self):
174176
serializer = UniqueForDateSerializer()
175177
expected = dedent("""
176-
UniqueForDateSerializer(validators=[<UniqueForDateValidator(queryset=UniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
178+
UniqueForDateSerializer():
177179
id = IntegerField(label='ID', read_only=True)
178180
slug = CharField(max_length=100)
179181
published = DateField(required=True)
182+
class Meta:
183+
validators = [<UniqueForDateValidator(queryset=UniqueForDateModel.objects.all(), field='slug', date_field='published')>]
180184
""")
181185
assert repr(serializer) == expected
182186

@@ -231,10 +235,12 @@ class Meta:
231235

232236
serializer = TestSerializer()
233237
expected = dedent("""
234-
TestSerializer(validators=[<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
238+
TestSerializer():
235239
id = IntegerField(label='ID', read_only=True)
236240
slug = CharField(max_length=100)
237241
published = HiddenField(default=CreateOnlyDefault(<function now>))
242+
class Meta:
243+
validators = [<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]
238244
""")
239245
assert repr(serializer) == expected
240246

@@ -246,9 +252,11 @@ class Meta:
246252

247253
serializer = TestSerializer()
248254
expected = dedent("""
249-
TestSerializer(validators=[<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]):
255+
TestSerializer():
250256
id = IntegerField(label='ID', read_only=True)
251257
slug = CharField(max_length=100)
252258
published = DateTimeField(default=CreateOnlyDefault(<function now>), read_only=True)
259+
class Meta:
260+
validators = [<UniqueForDateValidator(queryset=HiddenFieldUniqueForDateModel.objects.all(), field='slug', date_field='published')>]
253261
""")
254262
assert repr(serializer) == expected

0 commit comments

Comments
 (0)