Drop set_context() in favour of 'requires_context = True'

This commit is contained in:
Tom Christie 2019-11-21 13:45:55 +00:00
parent 79475fed7e
commit d57a40b579
4 changed files with 64 additions and 40 deletions

View File

@ -50,7 +50,19 @@ If set, this gives the default value that will be used for the field if no input
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.
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.
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.
For example:
class CurrentUserDefault:
"""
May be applied as a `default=...` value on a serializer field.
Returns the current user.
"""
requires_context = True
def __call__(self, serializer_field):
return serializer_field.context['request'].user
When serializing the instance, default will be used if the object attribute or dictionary key is not present in the instance.

View File

@ -294,15 +294,14 @@ To write a class-based validator, use the `__call__` method. Class-based validat
#### Accessing the context
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 using
`rest_framework.validators.ContextBasedValidator` as a base class for the
validator. The `__call__` method will then be called with the `serializer_field`
field it is being used with as additional context. You can do so by setting
a `requires_context = True` attribute on the validator. The `__call__` method
will then be called with the `serializer_field`
or `serializer` as an additional argument.
def __call__(self, value, serializer_field):
# Determine if this is an update or a create operation.
is_update = serializer_field.parent.instance is not None
requires_context = True
pass # implementation of the validator that uses `is_update`
def __call__(self, value, serializer_field):
...
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/

View File

@ -250,18 +250,29 @@ class CreateOnlyDefault:
for create operations, but that do not return any value for update
operations.
"""
requires_context = True
def __init__(self, default):
self.default = default
def set_context(self, serializer_field):
self.is_update = serializer_field.parent.instance is not None
if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update:
self.default.set_context(serializer_field)
def __call__(self):
if self.is_update:
def __call__(self, serializer_field):
is_update = serializer_field.parent.instance is not None
if is_update:
raise SkipField()
if callable(self.default):
if hasattr(self.default, 'set_context'):
warnings.warn(
"Method `set_context` on defaults is deprecated and will "
"no longer be called starting with 3.12. Instead set "
"`requires_context = True` on the class, and accept the "
"context as an additional argument.",
DeprecationWarning, stacklevel=2
)
self.default.set_context(self)
if getattr(self.default, 'requires_context', False):
return self.default(serializer_field)
else:
return self.default()
return self.default
@ -270,11 +281,10 @@ class CreateOnlyDefault:
class CurrentUserDefault:
def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user
requires_context = True
def __call__(self):
return self.user
def __call__(self, serializer_field):
return serializer_field.context['request'].user
def __repr__(self):
return '%s()' % self.__class__.__name__
@ -490,8 +500,20 @@ class Field:
raise SkipField()
if callable(self.default):
if hasattr(self.default, 'set_context'):
warnings.warn(
"Method `set_context` on defaults is deprecated and will "
"no longer be called starting with 3.12. Instead set "
"`requires_context = True` on the class, and accept the "
"context as an additional argument.",
DeprecationWarning, stacklevel=2
)
self.default.set_context(self)
if getattr(self.default, 'requires_context', False):
return self.default(self)
else:
return self.default()
return self.default
def validate_empty_values(self, data):
@ -549,22 +571,20 @@ class Field:
Test the given value against all the validators on the field,
and either raise a `ValidationError` or simply return.
"""
from rest_framework.validators import ContextBasedValidator
errors = []
for validator in self.validators:
if hasattr(validator, 'set_context'):
warnings.warn(
"Method `set_context` on validators is deprecated and will "
"no longer be called starting with 3.11. Instead derive the "
"validator from `rest_framwork.validators.ContextBasedValidator` "
"and accept the context as an additional argument.",
"no longer be called starting with 3.12. Instead set "
"`requires_context = True` on the class, and accept the "
"context as an additional argument.",
DeprecationWarning, stacklevel=2
)
validator.set_context(self)
try:
if isinstance(validator, ContextBasedValidator):
if getattr(validator, 'requires_context', False):
validator(value, self)
else:
validator(value)

View File

@ -30,23 +30,14 @@ def qs_filter(queryset, **kwargs):
return queryset.none()
class ContextBasedValidator:
"""Base class for validators that need a context during evaluation.
In extension to regular validators their `__call__` method must not only
accept a value, but also an instance of a serializer.
"""
def __call__(self, value, serializer):
raise NotImplementedError('`__call__()` must be implemented.')
class UniqueValidator(ContextBasedValidator):
class UniqueValidator:
"""
Validator that corresponds to `unique=True` on a model field.
Should be applied to an individual field on the serializer.
"""
message = _('This field must be unique.')
requires_context = True
def __init__(self, queryset, message=None, lookup='exact'):
self.queryset = queryset
@ -90,7 +81,7 @@ class UniqueValidator(ContextBasedValidator):
)
class UniqueTogetherValidator(ContextBasedValidator):
class UniqueTogetherValidator:
"""
Validator that corresponds to `unique_together = (...)` on a model class.
@ -98,6 +89,7 @@ class UniqueTogetherValidator(ContextBasedValidator):
"""
message = _('The fields {field_names} must make a unique set.')
missing_message = _('This field is required.')
requires_context = True
def __init__(self, queryset, fields, message=None):
self.queryset = queryset
@ -174,9 +166,10 @@ class UniqueTogetherValidator(ContextBasedValidator):
)
class BaseUniqueForValidator(ContextBasedValidator):
class BaseUniqueForValidator:
message = None
missing_message = _('This field is required.')
requires_context = True
def __init__(self, queryset, field, date_field, message=None):
self.queryset = queryset