Skip to content

Drop set_context() #7062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. This works the same way as for [validators](validators.md#using-set_context).
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.

Expand Down
16 changes: 10 additions & 6 deletions docs/api-guide/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,17 @@ To write a class-based validator, use the `__call__` method. Class-based validat
message = 'This field must be a multiple of %d.' % self.base
raise serializers.ValidationError(message)

#### Using `set_context()`
#### 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 declaring a `set_context` method on a class-based validator.
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 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 set_context(self, serializer_field):
# Determine if this is an update or a create operation.
# In `__call__` we can then use that information to modify the validation behavior.
self.is_update = serializer_field.parent.instance is not None
requires_context = True

def __call__(self, value, serializer_field):
...

[cite]: https://docs.djangoproject.com/en/stable/ref/validators/
61 changes: 47 additions & 14 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import inspect
import re
import uuid
import warnings
from collections import OrderedDict
from collections.abc import Mapping

Expand Down Expand Up @@ -249,31 +250,41 @@ 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):
return 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

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


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__
Expand Down Expand Up @@ -489,8 +500,20 @@ def get_default(self):
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)
return self.default()

if getattr(self.default, 'requires_context', False):
return self.default(self)
else:
return self.default()

return self.default

def validate_empty_values(self, data):
Expand Down Expand Up @@ -551,10 +574,20 @@ def run_validators(self, value):
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.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:
validator(value)
if getattr(validator, 'requires_context', False):
validator(value, self)
else:
validator(value)
except ValidationError as exc:
# If the validation error contains a mapping of fields to
# errors then simply raise it immediately rather than
Expand Down
Loading