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. 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. 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 #### Accessing the context
In some advanced cases you might want a validator to be passed the serializer 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 field it is being used with as additional context. You can do so by setting
`rest_framework.validators.ContextBasedValidator` as a base class for the a `requires_context = True` attribute on the validator. The `__call__` method
validator. The `__call__` method will then be called with the `serializer_field` will then be called with the `serializer_field`
or `serializer` as an additional argument. or `serializer` as an additional argument.
def __call__(self, value, serializer_field): requires_context = True
# Determine if this is an update or a create operation.
is_update = serializer_field.parent.instance is not None
pass # implementation of the validator that uses `is_update` def __call__(self, value, serializer_field):
...
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/ [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 for create operations, but that do not return any value for update
operations. operations.
""" """
requires_context = True
def __init__(self, default): def __init__(self, default):
self.default = default self.default = default
def set_context(self, serializer_field): def __call__(self, serializer_field):
self.is_update = serializer_field.parent.instance is not None is_update = serializer_field.parent.instance is not None
if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update: if is_update:
self.default.set_context(serializer_field)
def __call__(self):
if self.is_update:
raise SkipField() raise SkipField()
if callable(self.default): 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()
return self.default return self.default
@ -270,11 +281,10 @@ class CreateOnlyDefault:
class CurrentUserDefault: class CurrentUserDefault:
def set_context(self, serializer_field): requires_context = True
self.user = serializer_field.context['request'].user
def __call__(self): def __call__(self, serializer_field):
return self.user return serializer_field.context['request'].user
def __repr__(self): def __repr__(self):
return '%s()' % self.__class__.__name__ return '%s()' % self.__class__.__name__
@ -490,8 +500,20 @@ class Field:
raise SkipField() raise SkipField()
if callable(self.default): if callable(self.default):
if hasattr(self.default, 'set_context'): 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) self.default.set_context(self)
if getattr(self.default, 'requires_context', False):
return self.default(self)
else:
return self.default() return self.default()
return self.default return self.default
def validate_empty_values(self, data): def validate_empty_values(self, data):
@ -549,22 +571,20 @@ class Field:
Test the given value against all the validators on the field, Test the given value against all the validators on the field,
and either raise a `ValidationError` or simply return. and either raise a `ValidationError` or simply return.
""" """
from rest_framework.validators import ContextBasedValidator
errors = [] errors = []
for validator in self.validators: for validator in self.validators:
if hasattr(validator, 'set_context'): if hasattr(validator, 'set_context'):
warnings.warn( warnings.warn(
"Method `set_context` on validators is deprecated and will " "Method `set_context` on validators is deprecated and will "
"no longer be called starting with 3.11. Instead derive the " "no longer be called starting with 3.12. Instead set "
"validator from `rest_framwork.validators.ContextBasedValidator` " "`requires_context = True` on the class, and accept the "
"and accept the context as an additional argument.", "context as an additional argument.",
DeprecationWarning, stacklevel=2 DeprecationWarning, stacklevel=2
) )
validator.set_context(self) validator.set_context(self)
try: try:
if isinstance(validator, ContextBasedValidator): if getattr(validator, 'requires_context', False):
validator(value, self) validator(value, self)
else: else:
validator(value) validator(value)

View File

@ -30,23 +30,14 @@ def qs_filter(queryset, **kwargs):
return queryset.none() return queryset.none()
class ContextBasedValidator: class UniqueValidator:
"""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):
""" """
Validator that corresponds to `unique=True` on a model field. Validator that corresponds to `unique=True` on a model field.
Should be applied to an individual field on the serializer. Should be applied to an individual field on the serializer.
""" """
message = _('This field must be unique.') message = _('This field must be unique.')
requires_context = True
def __init__(self, queryset, message=None, lookup='exact'): def __init__(self, queryset, message=None, lookup='exact'):
self.queryset = queryset 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. 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.') message = _('The fields {field_names} must make a unique set.')
missing_message = _('This field is required.') missing_message = _('This field is required.')
requires_context = True
def __init__(self, queryset, fields, message=None): def __init__(self, queryset, fields, message=None):
self.queryset = queryset self.queryset = queryset
@ -174,9 +166,10 @@ class UniqueTogetherValidator(ContextBasedValidator):
) )
class BaseUniqueForValidator(ContextBasedValidator): class BaseUniqueForValidator:
message = None message = None
missing_message = _('This field is required.') missing_message = _('This field is required.')
requires_context = True
def __init__(self, queryset, field, date_field, message=None): def __init__(self, queryset, field, date_field, message=None):
self.queryset = queryset self.queryset = queryset