2022-10-05 13:02:00 +03:00
|
|
|
import contextlib
|
2018-07-06 13:18:17 +03:00
|
|
|
import sys
|
2023-04-08 09:27:14 +03:00
|
|
|
from operator import attrgetter
|
2019-04-30 18:53:44 +03:00
|
|
|
from urllib import parse
|
2015-09-22 17:35:38 +03:00
|
|
|
|
2015-06-25 23:55:51 +03:00
|
|
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
2015-07-14 15:31:24 +03:00
|
|
|
from django.db.models import Manager
|
2015-06-18 16:38:29 +03:00
|
|
|
from django.db.models.query import QuerySet
|
2017-11-10 11:41:03 +03:00
|
|
|
from django.urls import NoReverseMatch, Resolver404, get_script_prefix, resolve
|
2019-12-05 01:14:43 +03:00
|
|
|
from django.utils.encoding import smart_str, uri_to_iri
|
Replace all usage ugettext functions with the non-u versions (#6634)
On Python 3, the ugettext functions are a simple aliases of their non-u
counterparts (the 'u' represents Python 2 unicode type). Starting with
Django 3.0, the u versions will be deprecated.
https://docs.djangoproject.com/en/dev/releases/3.0/#id2
> django.utils.translation.ugettext(), ugettext_lazy(), ugettext_noop(),
> ungettext(), and ungettext_lazy() are deprecated in favor of the
> functions that they’re aliases for:
> django.utils.translation.gettext(), gettext_lazy(), gettext_noop(),
> ngettext(), and ngettext_lazy().
2019-05-01 08:49:54 +03:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2015-07-25 19:44:00 +03:00
|
|
|
from rest_framework.fields import (
|
2022-06-08 16:46:19 +03:00
|
|
|
Field, SkipField, empty, get_attribute, is_simple_callable, iter_options
|
2015-07-25 19:44:00 +03:00
|
|
|
)
|
2015-06-25 23:55:51 +03:00
|
|
|
from rest_framework.reverse import reverse
|
2016-10-10 15:03:46 +03:00
|
|
|
from rest_framework.settings import api_settings
|
2015-06-25 23:55:51 +03:00
|
|
|
from rest_framework.utils import html
|
2014-09-02 18:07:56 +04:00
|
|
|
|
|
|
|
|
2015-11-18 17:19:27 +03:00
|
|
|
def method_overridden(method_name, klass, instance):
|
|
|
|
"""
|
|
|
|
Determine if a method has been overridden.
|
|
|
|
"""
|
|
|
|
method = getattr(klass, method_name)
|
|
|
|
default_method = getattr(method, '__func__', method) # Python 3 compat
|
|
|
|
return default_method is not getattr(instance, method_name).__func__
|
2014-09-02 18:07:56 +04:00
|
|
|
|
|
|
|
|
2018-07-06 13:18:17 +03:00
|
|
|
class ObjectValueError(ValueError):
|
|
|
|
"""
|
|
|
|
Raised when `queryset.get()` failed due to an underlying `ValueError`.
|
|
|
|
Wrapping prevents calling code conflating this with unrelated errors.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class ObjectTypeError(TypeError):
|
|
|
|
"""
|
|
|
|
Raised when `queryset.get()` failed due to an underlying `TypeError`.
|
|
|
|
Wrapping prevents calling code conflating this with unrelated errors.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2019-04-30 18:53:44 +03:00
|
|
|
class Hyperlink(str):
|
2015-06-03 13:00:38 +03:00
|
|
|
"""
|
|
|
|
A string like object that additionally has an associated name.
|
|
|
|
We use this for hyperlinked URLs that may render as a named link
|
|
|
|
in some contexts, or render as a plain URL in others.
|
|
|
|
"""
|
2019-10-04 22:40:09 +03:00
|
|
|
def __new__(cls, url, obj):
|
2019-11-18 15:30:36 +03:00
|
|
|
ret = super().__new__(cls, url)
|
2016-10-11 14:18:00 +03:00
|
|
|
ret.obj = obj
|
2015-06-03 13:00:38 +03:00
|
|
|
return ret
|
|
|
|
|
2015-12-04 00:12:03 +03:00
|
|
|
def __getnewargs__(self):
|
2019-11-18 15:30:36 +03:00
|
|
|
return (str(self), self.name)
|
2015-12-04 00:12:03 +03:00
|
|
|
|
2016-10-11 14:18:00 +03:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
# This ensures that we only called `__str__` lazily,
|
|
|
|
# as in some cases calling __str__ on a model instances *might*
|
|
|
|
# involve a database lookup.
|
2019-04-30 18:53:44 +03:00
|
|
|
return str(self.obj)
|
2016-10-11 14:18:00 +03:00
|
|
|
|
2015-06-03 13:00:38 +03:00
|
|
|
is_hyperlink = True
|
|
|
|
|
|
|
|
|
2019-04-30 18:53:44 +03:00
|
|
|
class PKOnlyObject:
|
2014-11-14 00:11:13 +03:00
|
|
|
"""
|
|
|
|
This is a mock object, used for when we only need the pk of the object
|
|
|
|
instance, but still want to return an object with a .pk attribute,
|
|
|
|
in order to keep the same interface as a regular model instance.
|
|
|
|
"""
|
2023-04-08 09:27:14 +03:00
|
|
|
|
2014-10-16 23:47:34 +04:00
|
|
|
def __init__(self, pk):
|
|
|
|
self.pk = pk
|
|
|
|
|
2016-08-19 16:37:27 +03:00
|
|
|
def __str__(self):
|
|
|
|
return "%s" % self.pk
|
|
|
|
|
2014-11-14 00:11:13 +03:00
|
|
|
|
|
|
|
# We assume that 'validators' are intended for the child serializer,
|
|
|
|
# rather than the parent serializer.
|
2014-11-05 18:23:13 +03:00
|
|
|
MANY_RELATION_KWARGS = (
|
|
|
|
'read_only', 'write_only', 'required', 'default', 'initial', 'source',
|
2017-05-25 02:46:18 +03:00
|
|
|
'label', 'help_text', 'style', 'error_messages', 'allow_empty',
|
|
|
|
'html_cutoff', 'html_cutoff_text'
|
2014-11-05 18:23:13 +03:00
|
|
|
)
|
|
|
|
|
2014-10-16 23:47:34 +04:00
|
|
|
|
2014-09-02 18:07:56 +04:00
|
|
|
class RelatedField(Field):
|
2015-08-03 11:27:03 +03:00
|
|
|
queryset = None
|
2016-10-10 15:03:46 +03:00
|
|
|
html_cutoff = None
|
|
|
|
html_cutoff_text = None
|
2015-08-03 11:27:03 +03:00
|
|
|
|
2014-09-02 18:07:56 +04:00
|
|
|
def __init__(self, **kwargs):
|
2015-08-03 11:27:03 +03:00
|
|
|
self.queryset = kwargs.pop('queryset', self.queryset)
|
2017-05-25 02:46:18 +03:00
|
|
|
|
|
|
|
cutoff_from_settings = api_settings.HTML_SELECT_CUTOFF
|
|
|
|
if cutoff_from_settings is not None:
|
|
|
|
cutoff_from_settings = int(cutoff_from_settings)
|
|
|
|
self.html_cutoff = kwargs.pop('html_cutoff', cutoff_from_settings)
|
|
|
|
|
2016-10-10 15:03:46 +03:00
|
|
|
self.html_cutoff_text = kwargs.pop(
|
|
|
|
'html_cutoff_text',
|
|
|
|
self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT)
|
|
|
|
)
|
2015-11-18 17:19:27 +03:00
|
|
|
if not method_overridden('get_queryset', RelatedField, self):
|
2021-03-26 15:27:10 +03:00
|
|
|
assert self.queryset is not None or kwargs.get('read_only'), (
|
2015-11-14 00:16:27 +03:00
|
|
|
'Relational field must provide a `queryset` argument, '
|
2015-11-18 19:25:51 +03:00
|
|
|
'override `get_queryset`, or set read_only=`True`.'
|
2015-11-14 00:16:27 +03:00
|
|
|
)
|
2021-03-26 15:27:10 +03:00
|
|
|
assert not (self.queryset is not None and kwargs.get('read_only')), (
|
2014-09-12 20:03:42 +04:00
|
|
|
'Relational fields should not provide a `queryset` argument, '
|
|
|
|
'when setting read_only=`True`.'
|
|
|
|
)
|
2015-06-19 16:21:35 +03:00
|
|
|
kwargs.pop('many', None)
|
2015-07-30 19:03:08 +03:00
|
|
|
kwargs.pop('allow_empty', None)
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(**kwargs)
|
2014-09-02 18:07:56 +04:00
|
|
|
|
2014-09-18 17:23:00 +04:00
|
|
|
def __new__(cls, *args, **kwargs):
|
|
|
|
# We override this method in order to automagically create
|
2014-11-13 22:35:03 +03:00
|
|
|
# `ManyRelatedField` classes instead when `many=True` is set.
|
2014-09-18 17:23:00 +04:00
|
|
|
if kwargs.pop('many', False):
|
2014-11-14 00:11:13 +03:00
|
|
|
return cls.many_init(*args, **kwargs)
|
2019-04-30 18:53:44 +03:00
|
|
|
return super().__new__(cls, *args, **kwargs)
|
2014-09-18 17:23:00 +04:00
|
|
|
|
2014-11-14 00:11:13 +03:00
|
|
|
@classmethod
|
|
|
|
def many_init(cls, *args, **kwargs):
|
2014-11-26 14:30:28 +03:00
|
|
|
"""
|
|
|
|
This method handles creating a parent `ManyRelatedField` instance
|
|
|
|
when the `many=True` keyword argument is passed.
|
|
|
|
|
|
|
|
Typically you won't need to override this method.
|
|
|
|
|
|
|
|
Note that we're over-cautious in passing most arguments to both parent
|
|
|
|
and child classes in order to try to cover the general case. If you're
|
|
|
|
overriding this method you'll probably want something much simpler, eg:
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def many_init(cls, *args, **kwargs):
|
|
|
|
kwargs['child'] = cls()
|
|
|
|
return CustomManyRelatedField(*args, **kwargs)
|
|
|
|
"""
|
2014-11-14 00:11:13 +03:00
|
|
|
list_kwargs = {'child_relation': cls(*args, **kwargs)}
|
2018-01-08 12:49:46 +03:00
|
|
|
for key in kwargs:
|
2014-11-14 00:11:13 +03:00
|
|
|
if key in MANY_RELATION_KWARGS:
|
|
|
|
list_kwargs[key] = kwargs[key]
|
|
|
|
return ManyRelatedField(**list_kwargs)
|
|
|
|
|
2014-10-08 20:03:14 +04:00
|
|
|
def run_validation(self, data=empty):
|
|
|
|
# We force empty strings to None values for relational fields.
|
|
|
|
if data == '':
|
|
|
|
data = None
|
2019-04-30 18:53:44 +03:00
|
|
|
return super().run_validation(data)
|
2014-10-08 20:03:14 +04:00
|
|
|
|
2014-09-12 13:59:51 +04:00
|
|
|
def get_queryset(self):
|
|
|
|
queryset = self.queryset
|
2015-07-14 15:31:24 +03:00
|
|
|
if isinstance(queryset, (QuerySet, Manager)):
|
2014-09-12 13:59:51 +04:00
|
|
|
# Ensure queryset is re-evaluated whenever used.
|
2015-07-14 15:31:24 +03:00
|
|
|
# Note that actually a `Manager` class may also be used as the
|
|
|
|
# queryset argument. This occurs on ModelSerializer fields,
|
|
|
|
# as it allows us to generate a more expressive 'repr' output
|
|
|
|
# for the field.
|
|
|
|
# Eg: 'MyRelationship(queryset=ExampleModel.objects.all())'
|
2014-09-12 13:59:51 +04:00
|
|
|
queryset = queryset.all()
|
|
|
|
return queryset
|
|
|
|
|
2014-12-09 20:28:56 +03:00
|
|
|
def use_pk_only_optimization(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_attribute(self, instance):
|
2014-12-11 01:09:24 +03:00
|
|
|
if self.use_pk_only_optimization() and self.source_attrs:
|
2014-12-11 01:10:45 +03:00
|
|
|
# Optimized case, return a mock object only containing the pk attribute.
|
2022-10-05 13:02:00 +03:00
|
|
|
with contextlib.suppress(AttributeError):
|
2018-04-24 17:15:38 +03:00
|
|
|
attribute_instance = get_attribute(instance, self.source_attrs[:-1])
|
|
|
|
value = attribute_instance.serializable_value(self.source_attrs[-1])
|
2015-07-25 19:44:00 +03:00
|
|
|
if is_simple_callable(value):
|
2015-07-27 17:57:05 +03:00
|
|
|
# Handle edge case where the relationship `source` argument
|
2020-09-03 13:49:15 +03:00
|
|
|
# points to a `get_relationship()` method on the model.
|
|
|
|
value = value()
|
|
|
|
|
|
|
|
# Handle edge case where relationship `source` argument points
|
|
|
|
# to an instance instead of a pk (e.g., a `@property`).
|
|
|
|
value = getattr(value, 'pk', value)
|
|
|
|
|
2015-07-25 19:44:00 +03:00
|
|
|
return PKOnlyObject(pk=value)
|
2014-12-09 20:28:56 +03:00
|
|
|
# Standard case, return the object instance.
|
2019-04-30 18:53:44 +03:00
|
|
|
return super().get_attribute(instance)
|
2014-12-09 20:28:56 +03:00
|
|
|
|
2016-08-10 15:01:10 +03:00
|
|
|
def get_choices(self, cutoff=None):
|
2015-07-14 15:21:08 +03:00
|
|
|
queryset = self.get_queryset()
|
|
|
|
if queryset is None:
|
|
|
|
# Ensure that field.choices returns something sensible
|
|
|
|
# even when accessed with a read-only field.
|
|
|
|
return {}
|
|
|
|
|
2016-08-10 15:01:10 +03:00
|
|
|
if cutoff is not None:
|
|
|
|
queryset = queryset[:cutoff]
|
|
|
|
|
2023-04-30 12:20:02 +03:00
|
|
|
return {
|
|
|
|
self.to_representation(item): self.display_value(item) for item in queryset
|
|
|
|
}
|
2014-10-02 00:35:27 +04:00
|
|
|
|
2016-08-10 15:01:10 +03:00
|
|
|
@property
|
|
|
|
def choices(self):
|
|
|
|
return self.get_choices()
|
|
|
|
|
2015-08-07 16:36:00 +03:00
|
|
|
@property
|
|
|
|
def grouped_choices(self):
|
|
|
|
return self.choices
|
|
|
|
|
|
|
|
def iter_options(self):
|
2015-08-21 12:52:44 +03:00
|
|
|
return iter_options(
|
2016-08-10 15:01:10 +03:00
|
|
|
self.get_choices(cutoff=self.html_cutoff),
|
2015-08-21 12:52:44 +03:00
|
|
|
cutoff=self.html_cutoff,
|
|
|
|
cutoff_text=self.html_cutoff_text
|
|
|
|
)
|
2015-08-07 16:36:00 +03:00
|
|
|
|
2015-08-10 13:03:57 +03:00
|
|
|
def display_value(self, instance):
|
2019-04-30 18:53:44 +03:00
|
|
|
return str(instance)
|
2015-08-10 13:03:57 +03:00
|
|
|
|
2014-09-12 13:59:51 +04:00
|
|
|
|
2014-10-08 14:22:10 +04:00
|
|
|
class StringRelatedField(RelatedField):
|
2014-09-12 20:03:42 +04:00
|
|
|
"""
|
|
|
|
A read only field that represents its targets using their
|
|
|
|
plain string representation.
|
|
|
|
"""
|
|
|
|
|
2014-09-12 13:59:51 +04:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
kwargs['read_only'] = True
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(**kwargs)
|
2014-09-12 13:59:51 +04:00
|
|
|
|
|
|
|
def to_representation(self, value):
|
2019-04-30 18:53:44 +03:00
|
|
|
return str(value)
|
2014-09-02 18:07:56 +04:00
|
|
|
|
|
|
|
|
|
|
|
class PrimaryKeyRelatedField(RelatedField):
|
2014-09-12 20:03:42 +04:00
|
|
|
default_error_messages = {
|
2015-01-07 15:46:23 +03:00
|
|
|
'required': _('This field is required.'),
|
|
|
|
'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
|
|
|
|
'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
|
2014-09-02 18:07:56 +04:00
|
|
|
}
|
|
|
|
|
2015-04-02 07:45:12 +03:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
self.pk_field = kwargs.pop('pk_field', None)
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(**kwargs)
|
2015-04-02 07:45:12 +03:00
|
|
|
|
2014-12-09 20:28:56 +03:00
|
|
|
def use_pk_only_optimization(self):
|
|
|
|
return True
|
|
|
|
|
2014-09-12 13:59:51 +04:00
|
|
|
def to_internal_value(self, data):
|
2015-04-02 07:45:12 +03:00
|
|
|
if self.pk_field is not None:
|
|
|
|
data = self.pk_field.to_internal_value(data)
|
2020-10-10 20:02:21 +03:00
|
|
|
queryset = self.get_queryset()
|
2014-09-02 18:07:56 +04:00
|
|
|
try:
|
2021-03-17 16:28:38 +03:00
|
|
|
if isinstance(data, bool):
|
|
|
|
raise TypeError
|
2020-10-10 20:02:21 +03:00
|
|
|
return queryset.get(pk=data)
|
2014-09-02 18:07:56 +04:00
|
|
|
except ObjectDoesNotExist:
|
|
|
|
self.fail('does_not_exist', pk_value=data)
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
self.fail('incorrect_type', data_type=type(data).__name__)
|
|
|
|
|
2014-09-12 20:03:42 +04:00
|
|
|
def to_representation(self, value):
|
2015-04-02 07:45:12 +03:00
|
|
|
if self.pk_field is not None:
|
|
|
|
return self.pk_field.to_representation(value.pk)
|
2014-09-12 20:03:42 +04:00
|
|
|
return value.pk
|
|
|
|
|
2014-09-02 18:07:56 +04:00
|
|
|
|
|
|
|
class HyperlinkedRelatedField(RelatedField):
|
|
|
|
lookup_field = 'pk'
|
2015-08-03 11:27:03 +03:00
|
|
|
view_name = None
|
2014-09-02 18:07:56 +04:00
|
|
|
|
2014-09-12 20:03:42 +04:00
|
|
|
default_error_messages = {
|
2015-01-07 15:46:23 +03:00
|
|
|
'required': _('This field is required.'),
|
|
|
|
'no_match': _('Invalid hyperlink - No URL match.'),
|
|
|
|
'incorrect_match': _('Invalid hyperlink - Incorrect URL match.'),
|
|
|
|
'does_not_exist': _('Invalid hyperlink - Object does not exist.'),
|
|
|
|
'incorrect_type': _('Incorrect type. Expected URL string, received {data_type}.'),
|
2014-09-02 18:07:56 +04:00
|
|
|
}
|
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
def __init__(self, view_name=None, **kwargs):
|
2015-08-03 11:27:03 +03:00
|
|
|
if view_name is not None:
|
2015-08-07 17:41:46 +03:00
|
|
|
self.view_name = view_name
|
2015-08-03 11:27:03 +03:00
|
|
|
assert self.view_name is not None, 'The `view_name` argument is required.'
|
2014-09-02 18:07:56 +04:00
|
|
|
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
|
|
|
|
self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
|
2014-09-12 20:03:42 +04:00
|
|
|
self.format = kwargs.pop('format', None)
|
|
|
|
|
2015-02-05 03:58:09 +03:00
|
|
|
# We include this simply for dependency injection in tests.
|
|
|
|
# We can't add it as a class attributes or it would expect an
|
2014-10-03 00:00:33 +04:00
|
|
|
# implicit `self` argument to be passed.
|
2014-09-12 20:03:42 +04:00
|
|
|
self.reverse = reverse
|
|
|
|
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(**kwargs)
|
2014-09-02 18:07:56 +04:00
|
|
|
|
2014-12-09 20:28:56 +03:00
|
|
|
def use_pk_only_optimization(self):
|
|
|
|
return self.lookup_field == 'pk'
|
|
|
|
|
2014-09-02 18:07:56 +04:00
|
|
|
def get_object(self, view_name, view_args, view_kwargs):
|
|
|
|
"""
|
|
|
|
Return the object corresponding to a matched URL.
|
|
|
|
|
|
|
|
Takes the matched URL conf arguments, and should return an
|
|
|
|
object instance, or raise an `ObjectDoesNotExist` exception.
|
|
|
|
"""
|
|
|
|
lookup_value = view_kwargs[self.lookup_url_kwarg]
|
|
|
|
lookup_kwargs = {self.lookup_field: lookup_value}
|
2018-07-06 13:18:17 +03:00
|
|
|
queryset = self.get_queryset()
|
|
|
|
|
|
|
|
try:
|
|
|
|
return queryset.get(**lookup_kwargs)
|
|
|
|
except ValueError:
|
|
|
|
exc = ObjectValueError(str(sys.exc_info()[1]))
|
2019-04-30 18:53:44 +03:00
|
|
|
raise exc.with_traceback(sys.exc_info()[2])
|
2018-07-06 13:18:17 +03:00
|
|
|
except TypeError:
|
|
|
|
exc = ObjectTypeError(str(sys.exc_info()[1]))
|
2019-04-30 18:53:44 +03:00
|
|
|
raise exc.with_traceback(sys.exc_info()[2])
|
2014-09-02 18:07:56 +04:00
|
|
|
|
2014-09-12 20:03:42 +04:00
|
|
|
def get_url(self, obj, view_name, request, format):
|
|
|
|
"""
|
|
|
|
Given an object, return the URL that hyperlinks to the object.
|
|
|
|
|
|
|
|
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
|
|
|
|
attributes are not configured to correctly match the URL conf.
|
|
|
|
"""
|
|
|
|
# Unsaved objects will not yet have a valid URL.
|
2016-02-25 00:36:39 +03:00
|
|
|
if hasattr(obj, 'pk') and obj.pk in (None, ''):
|
2014-09-12 20:03:42 +04:00
|
|
|
return None
|
|
|
|
|
|
|
|
lookup_value = getattr(obj, self.lookup_field)
|
|
|
|
kwargs = {self.lookup_url_kwarg: lookup_value}
|
|
|
|
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
2021-03-26 15:27:10 +03:00
|
|
|
request = self.context.get('request')
|
2014-09-02 18:07:56 +04:00
|
|
|
try:
|
2014-09-12 20:03:42 +04:00
|
|
|
http_prefix = data.startswith(('http:', 'https:'))
|
2014-09-02 18:07:56 +04:00
|
|
|
except AttributeError:
|
2014-09-12 20:03:42 +04:00
|
|
|
self.fail('incorrect_type', data_type=type(data).__name__)
|
2014-09-02 18:07:56 +04:00
|
|
|
|
|
|
|
if http_prefix:
|
|
|
|
# If needed convert absolute URLs to relative path
|
2019-04-30 18:53:44 +03:00
|
|
|
data = parse.urlparse(data).path
|
2014-09-02 18:07:56 +04:00
|
|
|
prefix = get_script_prefix()
|
2014-09-12 20:03:42 +04:00
|
|
|
if data.startswith(prefix):
|
|
|
|
data = '/' + data[len(prefix):]
|
2014-09-02 18:07:56 +04:00
|
|
|
|
2019-11-21 14:38:40 +03:00
|
|
|
data = uri_to_iri(parse.unquote(data))
|
2017-04-14 02:56:44 +03:00
|
|
|
|
2014-09-02 18:07:56 +04:00
|
|
|
try:
|
2015-02-05 03:58:09 +03:00
|
|
|
match = resolve(data)
|
2014-09-12 20:06:37 +04:00
|
|
|
except Resolver404:
|
2014-09-02 18:07:56 +04:00
|
|
|
self.fail('no_match')
|
|
|
|
|
2015-02-05 03:58:09 +03:00
|
|
|
try:
|
|
|
|
expected_viewname = request.versioning_scheme.get_versioned_viewname(
|
|
|
|
self.view_name, request
|
|
|
|
)
|
|
|
|
except AttributeError:
|
|
|
|
expected_viewname = self.view_name
|
|
|
|
|
|
|
|
if match.view_name != expected_viewname:
|
2014-09-02 18:07:56 +04:00
|
|
|
self.fail('incorrect_match')
|
|
|
|
|
|
|
|
try:
|
|
|
|
return self.get_object(match.view_name, match.args, match.kwargs)
|
2018-07-06 13:18:17 +03:00
|
|
|
except (ObjectDoesNotExist, ObjectValueError, ObjectTypeError):
|
2014-09-02 18:07:56 +04:00
|
|
|
self.fail('does_not_exist')
|
|
|
|
|
2014-09-12 12:49:35 +04:00
|
|
|
def to_representation(self, value):
|
2016-06-02 17:03:17 +03:00
|
|
|
assert 'request' in self.context, (
|
2014-09-12 20:03:42 +04:00
|
|
|
"`%s` requires the request in the serializer"
|
2014-09-03 19:34:09 +04:00
|
|
|
" context. Add `context={'request': request}` when instantiating "
|
2014-09-12 20:03:42 +04:00
|
|
|
"the serializer." % self.__class__.__name__
|
2014-09-03 19:34:09 +04:00
|
|
|
)
|
|
|
|
|
2016-06-02 17:03:17 +03:00
|
|
|
request = self.context['request']
|
2021-03-26 15:27:10 +03:00
|
|
|
format = self.context.get('format')
|
2016-06-02 17:03:17 +03:00
|
|
|
|
2014-09-03 19:34:09 +04:00
|
|
|
# By default use whatever format is given for the current context
|
|
|
|
# unless the target is a different type to the source.
|
|
|
|
#
|
|
|
|
# Eg. Consider a HyperlinkedIdentityField pointing from a json
|
|
|
|
# representation to an html property of that representation...
|
|
|
|
#
|
|
|
|
# '/snippets/1/' should link to '/snippets/1/highlight/'
|
|
|
|
# ...but...
|
|
|
|
# '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
|
|
|
|
if format and self.format and self.format != format:
|
|
|
|
format = self.format
|
|
|
|
|
|
|
|
# Return the hyperlink, or error if incorrectly configured.
|
|
|
|
try:
|
2015-06-03 13:00:38 +03:00
|
|
|
url = self.get_url(value, self.view_name, request, format)
|
2014-09-03 19:34:09 +04:00
|
|
|
except NoReverseMatch:
|
|
|
|
msg = (
|
|
|
|
'Could not resolve URL for hyperlinked relationship using '
|
|
|
|
'view name "%s". You may have failed to include the related '
|
|
|
|
'model in your API, or incorrectly configured the '
|
|
|
|
'`lookup_field` attribute on this field.'
|
|
|
|
)
|
2015-07-02 13:36:14 +03:00
|
|
|
if value in ('', None):
|
|
|
|
value_string = {'': 'the empty string', None: 'None'}[value]
|
|
|
|
msg += (
|
|
|
|
" WARNING: The value of the field on the model instance "
|
|
|
|
"was %s, which may be why it didn't match any "
|
|
|
|
"entries in your URL conf." % value_string
|
|
|
|
)
|
2014-09-12 20:03:42 +04:00
|
|
|
raise ImproperlyConfigured(msg % self.view_name)
|
|
|
|
|
2015-06-03 13:00:38 +03:00
|
|
|
if url is None:
|
|
|
|
return None
|
|
|
|
|
2016-10-11 14:18:00 +03:00
|
|
|
return Hyperlink(url, value)
|
2015-06-03 13:00:38 +03:00
|
|
|
|
2014-09-12 20:03:42 +04:00
|
|
|
|
|
|
|
class HyperlinkedIdentityField(HyperlinkedRelatedField):
|
|
|
|
"""
|
|
|
|
A read-only field that represents the identity URL for an object, itself.
|
|
|
|
|
|
|
|
This is in contrast to `HyperlinkedRelatedField` which represents the
|
|
|
|
URL of relationships to other objects.
|
|
|
|
"""
|
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
def __init__(self, view_name=None, **kwargs):
|
|
|
|
assert view_name is not None, 'The `view_name` argument is required.'
|
2014-09-12 20:03:42 +04:00
|
|
|
kwargs['read_only'] = True
|
|
|
|
kwargs['source'] = '*'
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(view_name, **kwargs)
|
2014-09-03 19:34:09 +04:00
|
|
|
|
2014-12-09 20:28:56 +03:00
|
|
|
def use_pk_only_optimization(self):
|
|
|
|
# We have the complete object instance already. We don't need
|
|
|
|
# to run the 'only get the pk for this relationship' code.
|
|
|
|
return False
|
|
|
|
|
2014-09-02 18:07:56 +04:00
|
|
|
|
|
|
|
class SlugRelatedField(RelatedField):
|
2014-09-12 20:03:42 +04:00
|
|
|
"""
|
2015-10-29 18:20:44 +03:00
|
|
|
A read-write field that represents the target of the relationship
|
2014-09-12 20:03:42 +04:00
|
|
|
by a unique 'slug' attribute.
|
|
|
|
"""
|
|
|
|
default_error_messages = {
|
2015-01-07 15:46:23 +03:00
|
|
|
'does_not_exist': _('Object with {slug_name}={value} does not exist.'),
|
|
|
|
'invalid': _('Invalid value.'),
|
2014-09-12 20:03:42 +04:00
|
|
|
}
|
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
def __init__(self, slug_field=None, **kwargs):
|
|
|
|
assert slug_field is not None, 'The `slug_field` argument is required.'
|
2014-09-12 20:03:42 +04:00
|
|
|
self.slug_field = slug_field
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(**kwargs)
|
2014-09-12 20:03:42 +04:00
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
2020-10-10 20:02:21 +03:00
|
|
|
queryset = self.get_queryset()
|
2014-09-12 20:03:42 +04:00
|
|
|
try:
|
2020-10-10 20:02:21 +03:00
|
|
|
return queryset.get(**{self.slug_field: data})
|
2014-09-12 20:03:42 +04:00
|
|
|
except ObjectDoesNotExist:
|
2019-12-05 01:14:43 +03:00
|
|
|
self.fail('does_not_exist', slug_name=self.slug_field, value=smart_str(data))
|
2014-09-12 20:03:42 +04:00
|
|
|
except (TypeError, ValueError):
|
|
|
|
self.fail('invalid')
|
|
|
|
|
|
|
|
def to_representation(self, obj):
|
2023-04-08 09:27:14 +03:00
|
|
|
slug = self.slug_field
|
|
|
|
if "__" in slug:
|
|
|
|
# handling nested relationship if defined
|
|
|
|
slug = slug.replace('__', '.')
|
|
|
|
return attrgetter(slug)(obj)
|
2014-09-18 17:23:00 +04:00
|
|
|
|
|
|
|
|
2014-11-13 22:35:03 +03:00
|
|
|
class ManyRelatedField(Field):
|
2014-09-18 17:23:00 +04:00
|
|
|
"""
|
|
|
|
Relationships with `many=True` transparently get coerced into instead being
|
2014-11-13 22:35:03 +03:00
|
|
|
a ManyRelatedField with a child relationship.
|
2014-09-18 17:23:00 +04:00
|
|
|
|
2014-11-13 22:35:03 +03:00
|
|
|
The `ManyRelatedField` class is responsible for handling iterating through
|
2014-09-18 17:23:00 +04:00
|
|
|
the values and passing each one to the child relationship.
|
|
|
|
|
2014-11-26 14:38:48 +03:00
|
|
|
This class is treated as private API.
|
|
|
|
You shouldn't generally need to be using this class directly yourself,
|
|
|
|
and should instead simply set 'many=True' on the relationship.
|
2014-09-18 17:23:00 +04:00
|
|
|
"""
|
2014-10-02 19:24:24 +04:00
|
|
|
initial = []
|
2014-10-02 21:13:15 +04:00
|
|
|
default_empty_html = []
|
2015-07-16 15:51:15 +03:00
|
|
|
default_error_messages = {
|
|
|
|
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
|
|
|
|
'empty': _('This list may not be empty.')
|
|
|
|
}
|
2016-10-10 15:03:46 +03:00
|
|
|
html_cutoff = None
|
|
|
|
html_cutoff_text = None
|
2014-09-18 17:23:00 +04:00
|
|
|
|
|
|
|
def __init__(self, child_relation=None, *args, **kwargs):
|
|
|
|
self.child_relation = child_relation
|
2015-07-16 15:51:15 +03:00
|
|
|
self.allow_empty = kwargs.pop('allow_empty', True)
|
2017-05-25 02:46:18 +03:00
|
|
|
|
|
|
|
cutoff_from_settings = api_settings.HTML_SELECT_CUTOFF
|
|
|
|
if cutoff_from_settings is not None:
|
|
|
|
cutoff_from_settings = int(cutoff_from_settings)
|
|
|
|
self.html_cutoff = kwargs.pop('html_cutoff', cutoff_from_settings)
|
|
|
|
|
2016-10-10 15:03:46 +03:00
|
|
|
self.html_cutoff_text = kwargs.pop(
|
|
|
|
'html_cutoff_text',
|
|
|
|
self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT)
|
|
|
|
)
|
2014-09-18 17:23:00 +04:00
|
|
|
assert child_relation is not None, '`child_relation` is a required argument.'
|
2019-04-30 18:53:44 +03:00
|
|
|
super().__init__(*args, **kwargs)
|
2014-10-02 19:24:24 +04:00
|
|
|
self.child_relation.bind(field_name='', parent=self)
|
2014-09-18 17:23:00 +04:00
|
|
|
|
2014-10-09 18:11:19 +04:00
|
|
|
def get_value(self, dictionary):
|
|
|
|
# We override the default field access in order to support
|
|
|
|
# lists in HTML forms.
|
|
|
|
if html.is_html_input(dictionary):
|
2015-01-28 00:18:51 +03:00
|
|
|
# Don't return [] if the update is partial
|
|
|
|
if self.field_name not in dictionary:
|
|
|
|
if getattr(self.root, 'partial', False):
|
|
|
|
return empty
|
2014-10-09 18:11:19 +04:00
|
|
|
return dictionary.getlist(self.field_name)
|
2015-01-28 00:18:51 +03:00
|
|
|
|
2014-10-09 18:11:19 +04:00
|
|
|
return dictionary.get(self.field_name, empty)
|
|
|
|
|
2014-09-18 17:23:00 +04:00
|
|
|
def to_internal_value(self, data):
|
2019-04-30 18:53:44 +03:00
|
|
|
if isinstance(data, str) or not hasattr(data, '__iter__'):
|
2015-07-16 15:51:15 +03:00
|
|
|
self.fail('not_a_list', input_type=type(data).__name__)
|
|
|
|
if not self.allow_empty and len(data) == 0:
|
|
|
|
self.fail('empty')
|
|
|
|
|
2014-09-18 17:23:00 +04:00
|
|
|
return [
|
|
|
|
self.child_relation.to_internal_value(item)
|
|
|
|
for item in data
|
|
|
|
]
|
|
|
|
|
2014-10-16 23:47:34 +04:00
|
|
|
def get_attribute(self, instance):
|
2015-03-05 00:36:03 +03:00
|
|
|
# Can't have any relationships if not created
|
2015-03-23 18:40:33 +03:00
|
|
|
if hasattr(instance, 'pk') and instance.pk is None:
|
2015-03-05 00:36:03 +03:00
|
|
|
return []
|
|
|
|
|
2022-06-08 16:46:19 +03:00
|
|
|
try:
|
|
|
|
relationship = get_attribute(instance, self.source_attrs)
|
|
|
|
except (KeyError, AttributeError) as exc:
|
|
|
|
if self.default is not empty:
|
|
|
|
return self.get_default()
|
|
|
|
if self.allow_null:
|
|
|
|
return None
|
|
|
|
if not self.required:
|
|
|
|
raise SkipField()
|
|
|
|
msg = (
|
|
|
|
'Got {exc_type} when attempting to get a value for field '
|
|
|
|
'`{field}` on serializer `{serializer}`.\nThe serializer '
|
|
|
|
'field might be named incorrectly and not match '
|
|
|
|
'any attribute or key on the `{instance}` instance.\n'
|
|
|
|
'Original exception text was: {exc}.'.format(
|
|
|
|
exc_type=type(exc).__name__,
|
|
|
|
field=self.field_name,
|
|
|
|
serializer=self.parent.__class__.__name__,
|
|
|
|
instance=instance.__class__.__name__,
|
|
|
|
exc=exc
|
|
|
|
)
|
|
|
|
)
|
|
|
|
raise type(exc)(msg)
|
|
|
|
|
2017-01-08 19:09:23 +03:00
|
|
|
return relationship.all() if hasattr(relationship, 'all') else relationship
|
2014-10-16 23:47:34 +04:00
|
|
|
|
|
|
|
def to_representation(self, iterable):
|
2014-09-18 17:23:00 +04:00
|
|
|
return [
|
|
|
|
self.child_relation.to_representation(value)
|
2014-10-08 19:43:33 +04:00
|
|
|
for value in iterable
|
2014-09-18 17:23:00 +04:00
|
|
|
]
|
2014-10-02 00:35:27 +04:00
|
|
|
|
2016-08-10 15:01:10 +03:00
|
|
|
def get_choices(self, cutoff=None):
|
|
|
|
return self.child_relation.get_choices(cutoff)
|
|
|
|
|
2014-10-02 00:35:27 +04:00
|
|
|
@property
|
|
|
|
def choices(self):
|
2016-08-10 15:01:10 +03:00
|
|
|
return self.get_choices()
|
2015-08-07 16:36:00 +03:00
|
|
|
|
|
|
|
@property
|
|
|
|
def grouped_choices(self):
|
|
|
|
return self.choices
|
|
|
|
|
|
|
|
def iter_options(self):
|
2015-08-21 12:52:44 +03:00
|
|
|
return iter_options(
|
2016-08-10 15:01:10 +03:00
|
|
|
self.get_choices(cutoff=self.html_cutoff),
|
2015-08-21 12:52:44 +03:00
|
|
|
cutoff=self.html_cutoff,
|
|
|
|
cutoff_text=self.html_cutoff_text
|
|
|
|
)
|