Skip to content

Commit 070cff5

Browse files
authored
Drop set_context() (#7062)
* Do not persist the context in validators Fixes #5760 * Drop set_context() in favour of 'requires_context = True'
1 parent 9325c3f commit 070cff5

File tree

5 files changed

+131
-93
lines changed

5 files changed

+131
-93
lines changed

docs/api-guide/fields.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,19 @@ If set, this gives the default value that will be used for the field if no input
5050

5151
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.
5252

53-
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 `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context).
53+
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.
54+
55+
For example:
56+
57+
class CurrentUserDefault:
58+
"""
59+
May be applied as a `default=...` value on a serializer field.
60+
Returns the current user.
61+
"""
62+
requires_context = True
63+
64+
def __call__(self, serializer_field):
65+
return serializer_field.context['request'].user
5466

5567
When serializing the instance, default will be used if the object attribute or dictionary key is not present in the instance.
5668

docs/api-guide/validators.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,17 @@ To write a class-based validator, use the `__call__` method. Class-based validat
291291
message = 'This field must be a multiple of %d.' % self.base
292292
raise serializers.ValidationError(message)
293293

294-
#### Using `set_context()`
294+
#### Accessing the context
295295

296-
In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by declaring a `set_context` method on a class-based validator.
296+
In some advanced cases you might want a validator to be passed the serializer
297+
field it is being used with as additional context. You can do so by setting
298+
a `requires_context = True` attribute on the validator. The `__call__` method
299+
will then be called with the `serializer_field`
300+
or `serializer` as an additional argument.
297301

298-
def set_context(self, serializer_field):
299-
# Determine if this is an update or a create operation.
300-
# In `__call__` we can then use that information to modify the validation behavior.
301-
self.is_update = serializer_field.parent.instance is not None
302+
requires_context = True
303+
304+
def __call__(self, value, serializer_field):
305+
...
302306

303307
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/

rest_framework/fields.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import inspect
66
import re
77
import uuid
8+
import warnings
89
from collections import OrderedDict
910
from collections.abc import Mapping
1011

@@ -249,31 +250,41 @@ class CreateOnlyDefault:
249250
for create operations, but that do not return any value for update
250251
operations.
251252
"""
253+
requires_context = True
254+
252255
def __init__(self, default):
253256
self.default = default
254257

255-
def set_context(self, serializer_field):
256-
self.is_update = serializer_field.parent.instance is not None
257-
if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update:
258-
self.default.set_context(serializer_field)
259-
260-
def __call__(self):
261-
if self.is_update:
258+
def __call__(self, serializer_field):
259+
is_update = serializer_field.parent.instance is not None
260+
if is_update:
262261
raise SkipField()
263262
if callable(self.default):
264-
return self.default()
263+
if hasattr(self.default, 'set_context'):
264+
warnings.warn(
265+
"Method `set_context` on defaults is deprecated and will "
266+
"no longer be called starting with 3.12. Instead set "
267+
"`requires_context = True` on the class, and accept the "
268+
"context as an additional argument.",
269+
DeprecationWarning, stacklevel=2
270+
)
271+
self.default.set_context(self)
272+
273+
if getattr(self.default, 'requires_context', False):
274+
return self.default(serializer_field)
275+
else:
276+
return self.default()
265277
return self.default
266278

267279
def __repr__(self):
268280
return '%s(%s)' % (self.__class__.__name__, repr(self.default))
269281

270282

271283
class CurrentUserDefault:
272-
def set_context(self, serializer_field):
273-
self.user = serializer_field.context['request'].user
284+
requires_context = True
274285

275-
def __call__(self):
276-
return self.user
286+
def __call__(self, serializer_field):
287+
return serializer_field.context['request'].user
277288

278289
def __repr__(self):
279290
return '%s()' % self.__class__.__name__
@@ -489,8 +500,20 @@ def get_default(self):
489500
raise SkipField()
490501
if callable(self.default):
491502
if hasattr(self.default, 'set_context'):
503+
warnings.warn(
504+
"Method `set_context` on defaults is deprecated and will "
505+
"no longer be called starting with 3.12. Instead set "
506+
"`requires_context = True` on the class, and accept the "
507+
"context as an additional argument.",
508+
DeprecationWarning, stacklevel=2
509+
)
492510
self.default.set_context(self)
493-
return self.default()
511+
512+
if getattr(self.default, 'requires_context', False):
513+
return self.default(self)
514+
else:
515+
return self.default()
516+
494517
return self.default
495518

496519
def validate_empty_values(self, data):
@@ -551,10 +574,20 @@ def run_validators(self, value):
551574
errors = []
552575
for validator in self.validators:
553576
if hasattr(validator, 'set_context'):
577+
warnings.warn(
578+
"Method `set_context` on validators is deprecated and will "
579+
"no longer be called starting with 3.12. Instead set "
580+
"`requires_context = True` on the class, and accept the "
581+
"context as an additional argument.",
582+
DeprecationWarning, stacklevel=2
583+
)
554584
validator.set_context(self)
555585

556586
try:
557-
validator(value)
587+
if getattr(validator, 'requires_context', False):
588+
validator(value, self)
589+
else:
590+
validator(value)
558591
except ValidationError as exc:
559592
# If the validation error contains a mapping of fields to
560593
# errors then simply raise it immediately rather than

0 commit comments

Comments
 (0)