mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 17:47:04 +03:00
0dec36eb41
* Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
1733 lines
61 KiB
Python
1733 lines
61 KiB
Python
from __future__ import unicode_literals
|
|
|
|
import collections
|
|
import copy
|
|
import datetime
|
|
import decimal
|
|
import inspect
|
|
import json
|
|
import re
|
|
import uuid
|
|
from collections import OrderedDict
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.validators import (
|
|
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
|
|
MinValueValidator, RegexValidator, URLValidator, ip_address_validators
|
|
)
|
|
from django.forms import FilePathField as DjangoFilePathField
|
|
from django.forms import ImageField as DjangoImageField
|
|
from django.utils import six, timezone
|
|
from django.utils.dateparse import (
|
|
parse_date, parse_datetime, parse_duration, parse_time
|
|
)
|
|
from django.utils.duration import duration_string
|
|
from django.utils.encoding import is_protected_type, smart_text
|
|
from django.utils.formats import localize_input, sanitize_separators
|
|
from django.utils.functional import cached_property
|
|
from django.utils.ipv6 import clean_ipv6_address
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from rest_framework import ISO_8601
|
|
from rest_framework.compat import (
|
|
get_remote_field, unicode_repr, unicode_to_repr, value_from_object
|
|
)
|
|
from rest_framework.exceptions import ValidationError
|
|
from rest_framework.settings import api_settings
|
|
from rest_framework.utils import html, humanize_datetime, representation
|
|
|
|
|
|
class empty:
|
|
"""
|
|
This class is used to represent no data being provided for a given input
|
|
or output value.
|
|
|
|
It is required because `None` may be a valid input or output value.
|
|
"""
|
|
pass
|
|
|
|
|
|
if six.PY3:
|
|
def is_simple_callable(obj):
|
|
"""
|
|
True if the object is a callable that takes no arguments.
|
|
"""
|
|
if not callable(obj):
|
|
return False
|
|
|
|
sig = inspect.signature(obj)
|
|
params = sig.parameters.values()
|
|
return all(param.default != param.empty for param in params)
|
|
|
|
else:
|
|
def is_simple_callable(obj):
|
|
function = inspect.isfunction(obj)
|
|
method = inspect.ismethod(obj)
|
|
|
|
if not (function or method):
|
|
return False
|
|
|
|
if method:
|
|
is_unbound = obj.im_self is None
|
|
|
|
args, _, _, defaults = inspect.getargspec(obj)
|
|
|
|
len_args = len(args) if function or is_unbound else len(args) - 1
|
|
len_defaults = len(defaults) if defaults else 0
|
|
return len_args <= len_defaults
|
|
|
|
|
|
def get_attribute(instance, attrs):
|
|
"""
|
|
Similar to Python's built in `getattr(instance, attr)`,
|
|
but takes a list of nested attributes, instead of a single attribute.
|
|
|
|
Also accepts either attribute lookup on objects or dictionary lookups.
|
|
"""
|
|
for attr in attrs:
|
|
if instance is None:
|
|
# Break out early if we get `None` at any point in a nested lookup.
|
|
return None
|
|
try:
|
|
if isinstance(instance, collections.Mapping):
|
|
instance = instance[attr]
|
|
else:
|
|
instance = getattr(instance, attr)
|
|
except ObjectDoesNotExist:
|
|
return None
|
|
if is_simple_callable(instance):
|
|
try:
|
|
instance = instance()
|
|
except (AttributeError, KeyError) as exc:
|
|
# If we raised an Attribute or KeyError here it'd get treated
|
|
# as an omitted field in `Field.get_attribute()`. Instead we
|
|
# raise a ValueError to ensure the exception is not masked.
|
|
raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))
|
|
|
|
return instance
|
|
|
|
|
|
def set_value(dictionary, keys, value):
|
|
"""
|
|
Similar to Python's built in `dictionary[key] = value`,
|
|
but takes a list of nested keys instead of a single key.
|
|
|
|
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
|
|
set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
|
|
set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
|
|
"""
|
|
if not keys:
|
|
dictionary.update(value)
|
|
return
|
|
|
|
for key in keys[:-1]:
|
|
if key not in dictionary:
|
|
dictionary[key] = {}
|
|
dictionary = dictionary[key]
|
|
|
|
dictionary[keys[-1]] = value
|
|
|
|
|
|
def to_choices_dict(choices):
|
|
"""
|
|
Convert choices into key/value dicts.
|
|
|
|
to_choices_dict([1]) -> {1: 1}
|
|
to_choices_dict([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
|
|
to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2nd'}}
|
|
"""
|
|
# Allow single, paired or grouped choices style:
|
|
# choices = [1, 2, 3]
|
|
# choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
|
|
# choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
|
|
ret = OrderedDict()
|
|
for choice in choices:
|
|
if (not isinstance(choice, (list, tuple))):
|
|
# single choice
|
|
ret[choice] = choice
|
|
else:
|
|
key, value = choice
|
|
if isinstance(value, (list, tuple)):
|
|
# grouped choices (category, sub choices)
|
|
ret[key] = to_choices_dict(value)
|
|
else:
|
|
# paired choice (key, display value)
|
|
ret[key] = value
|
|
return ret
|
|
|
|
|
|
def flatten_choices_dict(choices):
|
|
"""
|
|
Convert a group choices dict into a flat dict of choices.
|
|
|
|
flatten_choices_dict({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'}
|
|
flatten_choices_dict({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'}
|
|
"""
|
|
ret = OrderedDict()
|
|
for key, value in choices.items():
|
|
if isinstance(value, dict):
|
|
# grouped choices (category, sub choices)
|
|
for sub_key, sub_value in value.items():
|
|
ret[sub_key] = sub_value
|
|
else:
|
|
# choice (key, display value)
|
|
ret[key] = value
|
|
return ret
|
|
|
|
|
|
def iter_options(grouped_choices, cutoff=None, cutoff_text=None):
|
|
"""
|
|
Helper function for options and option groups in templates.
|
|
"""
|
|
class StartOptionGroup(object):
|
|
start_option_group = True
|
|
end_option_group = False
|
|
|
|
def __init__(self, label):
|
|
self.label = label
|
|
|
|
class EndOptionGroup(object):
|
|
start_option_group = False
|
|
end_option_group = True
|
|
|
|
class Option(object):
|
|
start_option_group = False
|
|
end_option_group = False
|
|
|
|
def __init__(self, value, display_text, disabled=False):
|
|
self.value = value
|
|
self.display_text = display_text
|
|
self.disabled = disabled
|
|
|
|
count = 0
|
|
|
|
for key, value in grouped_choices.items():
|
|
if cutoff and count >= cutoff:
|
|
break
|
|
|
|
if isinstance(value, dict):
|
|
yield StartOptionGroup(label=key)
|
|
for sub_key, sub_value in value.items():
|
|
if cutoff and count >= cutoff:
|
|
break
|
|
yield Option(value=sub_key, display_text=sub_value)
|
|
count += 1
|
|
yield EndOptionGroup()
|
|
else:
|
|
yield Option(value=key, display_text=value)
|
|
count += 1
|
|
|
|
if cutoff and count >= cutoff and cutoff_text:
|
|
cutoff_text = cutoff_text.format(count=cutoff)
|
|
yield Option(value='n/a', display_text=cutoff_text, disabled=True)
|
|
|
|
|
|
class CreateOnlyDefault(object):
|
|
"""
|
|
This class may be used to provide default values that are only used
|
|
for create operations, but that do not return any value for update
|
|
operations.
|
|
"""
|
|
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:
|
|
raise SkipField()
|
|
if callable(self.default):
|
|
return self.default()
|
|
return self.default
|
|
|
|
def __repr__(self):
|
|
return unicode_to_repr(
|
|
'%s(%s)' % (self.__class__.__name__, unicode_repr(self.default))
|
|
)
|
|
|
|
|
|
class CurrentUserDefault(object):
|
|
def set_context(self, serializer_field):
|
|
self.user = serializer_field.context['request'].user
|
|
|
|
def __call__(self):
|
|
return self.user
|
|
|
|
def __repr__(self):
|
|
return unicode_to_repr('%s()' % self.__class__.__name__)
|
|
|
|
|
|
class SkipField(Exception):
|
|
pass
|
|
|
|
|
|
REGEX_TYPE = type(re.compile(''))
|
|
|
|
NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`'
|
|
NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`'
|
|
NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
|
|
USE_READONLYFIELD = 'Field(read_only=True) should be ReadOnlyField'
|
|
MISSING_ERROR_MESSAGE = (
|
|
'ValidationError raised by `{class_name}`, but error key `{key}` does '
|
|
'not exist in the `error_messages` dictionary.'
|
|
)
|
|
|
|
|
|
class Field(object):
|
|
_creation_counter = 0
|
|
|
|
default_error_messages = {
|
|
'required': _('This field is required.'),
|
|
'null': _('This field may not be null.')
|
|
}
|
|
default_validators = []
|
|
default_empty_html = empty
|
|
initial = None
|
|
|
|
def __init__(self, read_only=False, write_only=False,
|
|
required=None, default=empty, initial=empty, source=None,
|
|
label=None, help_text=None, style=None,
|
|
error_messages=None, validators=None, allow_null=False):
|
|
self._creation_counter = Field._creation_counter
|
|
Field._creation_counter += 1
|
|
|
|
# If `required` is unset, then use `True` unless a default is provided.
|
|
if required is None:
|
|
required = default is empty and not read_only
|
|
|
|
# Some combinations of keyword arguments do not make sense.
|
|
assert not (read_only and write_only), NOT_READ_ONLY_WRITE_ONLY
|
|
assert not (read_only and required), NOT_READ_ONLY_REQUIRED
|
|
assert not (required and default is not empty), NOT_REQUIRED_DEFAULT
|
|
assert not (read_only and self.__class__ == Field), USE_READONLYFIELD
|
|
|
|
self.read_only = read_only
|
|
self.write_only = write_only
|
|
self.required = required
|
|
self.default = default
|
|
self.source = source
|
|
self.initial = self.initial if (initial is empty) else initial
|
|
self.label = label
|
|
self.help_text = help_text
|
|
self.style = {} if style is None else style
|
|
self.allow_null = allow_null
|
|
|
|
if self.default_empty_html is not empty:
|
|
if default is not empty:
|
|
self.default_empty_html = default
|
|
|
|
if validators is not None:
|
|
self.validators = validators[:]
|
|
|
|
# These are set up by `.bind()` when the field is added to a serializer.
|
|
self.field_name = None
|
|
self.parent = None
|
|
|
|
# Collect default error message from self and parent classes
|
|
messages = {}
|
|
for cls in reversed(self.__class__.__mro__):
|
|
messages.update(getattr(cls, 'default_error_messages', {}))
|
|
messages.update(error_messages or {})
|
|
self.error_messages = messages
|
|
|
|
def bind(self, field_name, parent):
|
|
"""
|
|
Initializes the field name and parent for the field instance.
|
|
Called when a field is added to the parent serializer instance.
|
|
"""
|
|
|
|
# In order to enforce a consistent style, we error if a redundant
|
|
# 'source' argument has been used. For example:
|
|
# my_field = serializer.CharField(source='my_field')
|
|
assert self.source != field_name, (
|
|
"It is redundant to specify `source='%s'` on field '%s' in "
|
|
"serializer '%s', because it is the same as the field name. "
|
|
"Remove the `source` keyword argument." %
|
|
(field_name, self.__class__.__name__, parent.__class__.__name__)
|
|
)
|
|
|
|
self.field_name = field_name
|
|
self.parent = parent
|
|
|
|
# `self.label` should default to being based on the field name.
|
|
if self.label is None:
|
|
self.label = field_name.replace('_', ' ').capitalize()
|
|
|
|
# self.source should default to being the same as the field name.
|
|
if self.source is None:
|
|
self.source = field_name
|
|
|
|
# self.source_attrs is a list of attributes that need to be looked up
|
|
# when serializing the instance, or populating the validated data.
|
|
if self.source == '*':
|
|
self.source_attrs = []
|
|
else:
|
|
self.source_attrs = self.source.split('.')
|
|
|
|
# .validators is a lazily loaded property, that gets its default
|
|
# value from `get_validators`.
|
|
@property
|
|
def validators(self):
|
|
if not hasattr(self, '_validators'):
|
|
self._validators = self.get_validators()
|
|
return self._validators
|
|
|
|
@validators.setter
|
|
def validators(self, validators):
|
|
self._validators = validators
|
|
|
|
def get_validators(self):
|
|
return self.default_validators[:]
|
|
|
|
def get_initial(self):
|
|
"""
|
|
Return a value to use when the field is being returned as a primitive
|
|
value, without any object instance.
|
|
"""
|
|
if callable(self.initial):
|
|
return self.initial()
|
|
return self.initial
|
|
|
|
def get_value(self, dictionary):
|
|
"""
|
|
Given the *incoming* primitive data, return the value for this field
|
|
that should be validated and transformed to a native value.
|
|
"""
|
|
if html.is_html_input(dictionary):
|
|
# HTML forms will represent empty fields as '', and cannot
|
|
# represent None or False values directly.
|
|
if self.field_name not in dictionary:
|
|
if getattr(self.root, 'partial', False):
|
|
return empty
|
|
return self.default_empty_html
|
|
ret = dictionary[self.field_name]
|
|
if ret == '' and self.allow_null:
|
|
# If the field is blank, and null is a valid value then
|
|
# determine if we should use null instead.
|
|
return '' if getattr(self, 'allow_blank', False) else None
|
|
elif ret == '' and not self.required:
|
|
# If the field is blank, and emptiness is valid then
|
|
# determine if we should use emptiness instead.
|
|
return '' if getattr(self, 'allow_blank', False) else empty
|
|
return ret
|
|
return dictionary.get(self.field_name, empty)
|
|
|
|
def get_attribute(self, instance):
|
|
"""
|
|
Given the *outgoing* object instance, return the primitive value
|
|
that should be used for this field.
|
|
"""
|
|
try:
|
|
return get_attribute(instance, self.source_attrs)
|
|
except (KeyError, AttributeError) as exc:
|
|
if not self.required and self.default is empty:
|
|
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)
|
|
|
|
def get_default(self):
|
|
"""
|
|
Return the default value to use when validating data if no input
|
|
is provided for this field.
|
|
|
|
If a default has not been set for this field then this will simply
|
|
raise `SkipField`, indicating that no value should be set in the
|
|
validated data for this field.
|
|
"""
|
|
if self.default is empty or getattr(self.root, 'partial', False):
|
|
# No default, or this is a partial update.
|
|
raise SkipField()
|
|
if callable(self.default):
|
|
if hasattr(self.default, 'set_context'):
|
|
self.default.set_context(self)
|
|
return self.default()
|
|
return self.default
|
|
|
|
def validate_empty_values(self, data):
|
|
"""
|
|
Validate empty values, and either:
|
|
|
|
* Raise `ValidationError`, indicating invalid data.
|
|
* Raise `SkipField`, indicating that the field should be ignored.
|
|
* Return (True, data), indicating an empty value that should be
|
|
returned without any further validation being applied.
|
|
* Return (False, data), indicating a non-empty value, that should
|
|
have validation applied as normal.
|
|
"""
|
|
if self.read_only:
|
|
return (True, self.get_default())
|
|
|
|
if data is empty:
|
|
if getattr(self.root, 'partial', False):
|
|
raise SkipField()
|
|
if self.required:
|
|
self.fail('required')
|
|
return (True, self.get_default())
|
|
|
|
if data is None:
|
|
if not self.allow_null:
|
|
self.fail('null')
|
|
return (True, None)
|
|
|
|
return (False, data)
|
|
|
|
def run_validation(self, data=empty):
|
|
"""
|
|
Validate a simple representation and return the internal value.
|
|
|
|
The provided data may be `empty` if no representation was included
|
|
in the input.
|
|
|
|
May raise `SkipField` if the field should not be included in the
|
|
validated data.
|
|
"""
|
|
(is_empty_value, data) = self.validate_empty_values(data)
|
|
if is_empty_value:
|
|
return data
|
|
value = self.to_internal_value(data)
|
|
self.run_validators(value)
|
|
return value
|
|
|
|
def run_validators(self, value):
|
|
"""
|
|
Test the given value against all the validators on the field,
|
|
and either raise a `ValidationError` or simply return.
|
|
"""
|
|
errors = []
|
|
for validator in self.validators:
|
|
if hasattr(validator, 'set_context'):
|
|
validator.set_context(self)
|
|
|
|
try:
|
|
validator(value)
|
|
except ValidationError as exc:
|
|
# If the validation error contains a mapping of fields to
|
|
# errors then simply raise it immediately rather than
|
|
# attempting to accumulate a list of errors.
|
|
if isinstance(exc.detail, dict):
|
|
raise
|
|
errors.extend(exc.detail)
|
|
except DjangoValidationError as exc:
|
|
errors.extend(exc.messages)
|
|
if errors:
|
|
raise ValidationError(errors)
|
|
|
|
def to_internal_value(self, data):
|
|
"""
|
|
Transform the *incoming* primitive data into a native value.
|
|
"""
|
|
raise NotImplementedError(
|
|
'{cls}.to_internal_value() must be implemented.'.format(
|
|
cls=self.__class__.__name__
|
|
)
|
|
)
|
|
|
|
def to_representation(self, value):
|
|
"""
|
|
Transform the *outgoing* native value into primitive data.
|
|
"""
|
|
raise NotImplementedError(
|
|
'{cls}.to_representation() must be implemented for field '
|
|
'{field_name}. If you do not need to support write operations '
|
|
'you probably want to subclass `ReadOnlyField` instead.'.format(
|
|
cls=self.__class__.__name__,
|
|
field_name=self.field_name,
|
|
)
|
|
)
|
|
|
|
def fail(self, key, **kwargs):
|
|
"""
|
|
A helper method that simply raises a validation error.
|
|
"""
|
|
try:
|
|
msg = self.error_messages[key]
|
|
except KeyError:
|
|
class_name = self.__class__.__name__
|
|
msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
|
|
raise AssertionError(msg)
|
|
message_string = msg.format(**kwargs)
|
|
raise ValidationError(message_string)
|
|
|
|
@cached_property
|
|
def root(self):
|
|
"""
|
|
Returns the top-level serializer for this field.
|
|
"""
|
|
root = self
|
|
while root.parent is not None:
|
|
root = root.parent
|
|
return root
|
|
|
|
@cached_property
|
|
def context(self):
|
|
"""
|
|
Returns the context as passed to the root serializer on initialization.
|
|
"""
|
|
return getattr(self.root, '_context', {})
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
"""
|
|
When a field is instantiated, we store the arguments that were used,
|
|
so that we can present a helpful representation of the object.
|
|
"""
|
|
instance = super(Field, cls).__new__(cls)
|
|
instance._args = args
|
|
instance._kwargs = kwargs
|
|
return instance
|
|
|
|
def __deepcopy__(self, memo):
|
|
"""
|
|
When cloning fields we instantiate using the arguments it was
|
|
originally created with, rather than copying the complete state.
|
|
"""
|
|
# Treat regexes and validators as immutable.
|
|
# See https://github.com/tomchristie/django-rest-framework/issues/1954
|
|
# and https://github.com/tomchristie/django-rest-framework/pull/4489
|
|
args = [
|
|
copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item
|
|
for item in self._args
|
|
]
|
|
kwargs = {
|
|
key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value)
|
|
for key, value in self._kwargs.items()
|
|
}
|
|
return self.__class__(*args, **kwargs)
|
|
|
|
def __repr__(self):
|
|
"""
|
|
Fields are represented using their initial calling arguments.
|
|
This allows us to create descriptive representations for serializer
|
|
instances that show all the declared fields on the serializer.
|
|
"""
|
|
return unicode_to_repr(representation.field_repr(self))
|
|
|
|
|
|
# Boolean types...
|
|
|
|
class BooleanField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('"{input}" is not a valid boolean.')
|
|
}
|
|
default_empty_html = False
|
|
initial = False
|
|
TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
|
|
FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
|
|
|
|
def __init__(self, **kwargs):
|
|
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.'
|
|
super(BooleanField, self).__init__(**kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
try:
|
|
if data in self.TRUE_VALUES:
|
|
return True
|
|
elif data in self.FALSE_VALUES:
|
|
return False
|
|
except TypeError: # Input is an unhashable type
|
|
pass
|
|
self.fail('invalid', input=data)
|
|
|
|
def to_representation(self, value):
|
|
if value in self.TRUE_VALUES:
|
|
return True
|
|
elif value in self.FALSE_VALUES:
|
|
return False
|
|
return bool(value)
|
|
|
|
|
|
class NullBooleanField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('"{input}" is not a valid boolean.')
|
|
}
|
|
initial = None
|
|
TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
|
|
FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
|
|
NULL_VALUES = {'n', 'N', 'null', 'Null', 'NULL', '', None}
|
|
|
|
def __init__(self, **kwargs):
|
|
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
|
|
kwargs['allow_null'] = True
|
|
super(NullBooleanField, self).__init__(**kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
if data in self.TRUE_VALUES:
|
|
return True
|
|
elif data in self.FALSE_VALUES:
|
|
return False
|
|
elif data in self.NULL_VALUES:
|
|
return None
|
|
self.fail('invalid', input=data)
|
|
|
|
def to_representation(self, value):
|
|
if value in self.NULL_VALUES:
|
|
return None
|
|
if value in self.TRUE_VALUES:
|
|
return True
|
|
elif value in self.FALSE_VALUES:
|
|
return False
|
|
return bool(value)
|
|
|
|
|
|
# String types...
|
|
|
|
class CharField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('Not a valid string.'),
|
|
'blank': _('This field may not be blank.'),
|
|
'max_length': _('Ensure this field has no more than {max_length} characters.'),
|
|
'min_length': _('Ensure this field has at least {min_length} characters.')
|
|
}
|
|
initial = ''
|
|
|
|
def __init__(self, **kwargs):
|
|
self.allow_blank = kwargs.pop('allow_blank', False)
|
|
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
|
|
self.max_length = kwargs.pop('max_length', None)
|
|
self.min_length = kwargs.pop('min_length', None)
|
|
super(CharField, self).__init__(**kwargs)
|
|
if self.max_length is not None:
|
|
message = self.error_messages['max_length'].format(max_length=self.max_length)
|
|
self.validators.append(MaxLengthValidator(self.max_length, message=message))
|
|
if self.min_length is not None:
|
|
message = self.error_messages['min_length'].format(min_length=self.min_length)
|
|
self.validators.append(MinLengthValidator(self.min_length, message=message))
|
|
|
|
def run_validation(self, data=empty):
|
|
# Test for the empty string here so that it does not get validated,
|
|
# and so that subclasses do not need to handle it explicitly
|
|
# inside the `to_internal_value()` method.
|
|
if data == '' or (self.trim_whitespace and six.text_type(data).strip() == ''):
|
|
if not self.allow_blank:
|
|
self.fail('blank')
|
|
return ''
|
|
return super(CharField, self).run_validation(data)
|
|
|
|
def to_internal_value(self, data):
|
|
# We're lenient with allowing basic numerics to be coerced into strings,
|
|
# but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
|
|
# and composites such as lists are likely user error.
|
|
if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)):
|
|
self.fail('invalid')
|
|
value = six.text_type(data)
|
|
return value.strip() if self.trim_whitespace else value
|
|
|
|
def to_representation(self, value):
|
|
return six.text_type(value)
|
|
|
|
|
|
class EmailField(CharField):
|
|
default_error_messages = {
|
|
'invalid': _('Enter a valid email address.')
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super(EmailField, self).__init__(**kwargs)
|
|
validator = EmailValidator(message=self.error_messages['invalid'])
|
|
self.validators.append(validator)
|
|
|
|
|
|
class RegexField(CharField):
|
|
default_error_messages = {
|
|
'invalid': _('This value does not match the required pattern.')
|
|
}
|
|
|
|
def __init__(self, regex, **kwargs):
|
|
super(RegexField, self).__init__(**kwargs)
|
|
validator = RegexValidator(regex, message=self.error_messages['invalid'])
|
|
self.validators.append(validator)
|
|
|
|
|
|
class SlugField(CharField):
|
|
default_error_messages = {
|
|
'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.')
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super(SlugField, self).__init__(**kwargs)
|
|
slug_regex = re.compile(r'^[-a-zA-Z0-9_]+$')
|
|
validator = RegexValidator(slug_regex, message=self.error_messages['invalid'])
|
|
self.validators.append(validator)
|
|
|
|
|
|
class URLField(CharField):
|
|
default_error_messages = {
|
|
'invalid': _('Enter a valid URL.')
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
super(URLField, self).__init__(**kwargs)
|
|
validator = URLValidator(message=self.error_messages['invalid'])
|
|
self.validators.append(validator)
|
|
|
|
|
|
class UUIDField(Field):
|
|
valid_formats = ('hex_verbose', 'hex', 'int', 'urn')
|
|
|
|
default_error_messages = {
|
|
'invalid': _('"{value}" is not a valid UUID.'),
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
self.uuid_format = kwargs.pop('format', 'hex_verbose')
|
|
if self.uuid_format not in self.valid_formats:
|
|
raise ValueError(
|
|
'Invalid format for uuid representation. '
|
|
'Must be one of "{0}"'.format('", "'.join(self.valid_formats))
|
|
)
|
|
super(UUIDField, self).__init__(**kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
if not isinstance(data, uuid.UUID):
|
|
try:
|
|
if isinstance(data, six.integer_types):
|
|
return uuid.UUID(int=data)
|
|
elif isinstance(data, six.string_types):
|
|
return uuid.UUID(hex=data)
|
|
else:
|
|
self.fail('invalid', value=data)
|
|
except (ValueError):
|
|
self.fail('invalid', value=data)
|
|
return data
|
|
|
|
def to_representation(self, value):
|
|
if self.uuid_format == 'hex_verbose':
|
|
return str(value)
|
|
else:
|
|
return getattr(value, self.uuid_format)
|
|
|
|
|
|
class IPAddressField(CharField):
|
|
"""Support both IPAddressField and GenericIPAddressField"""
|
|
|
|
default_error_messages = {
|
|
'invalid': _('Enter a valid IPv4 or IPv6 address.'),
|
|
}
|
|
|
|
def __init__(self, protocol='both', **kwargs):
|
|
self.protocol = protocol.lower()
|
|
self.unpack_ipv4 = (self.protocol == 'both')
|
|
super(IPAddressField, self).__init__(**kwargs)
|
|
validators, error_message = ip_address_validators(protocol, self.unpack_ipv4)
|
|
self.validators.extend(validators)
|
|
|
|
def to_internal_value(self, data):
|
|
if not isinstance(data, six.string_types):
|
|
self.fail('invalid', value=data)
|
|
|
|
if ':' in data:
|
|
try:
|
|
if self.protocol in ('both', 'ipv6'):
|
|
return clean_ipv6_address(data, self.unpack_ipv4)
|
|
except DjangoValidationError:
|
|
self.fail('invalid', value=data)
|
|
|
|
return super(IPAddressField, self).to_internal_value(data)
|
|
|
|
|
|
# Number types...
|
|
|
|
class IntegerField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('A valid integer is required.'),
|
|
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
|
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
|
|
'max_string_length': _('String value too large.')
|
|
}
|
|
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
|
|
re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2'
|
|
|
|
def __init__(self, **kwargs):
|
|
self.max_value = kwargs.pop('max_value', None)
|
|
self.min_value = kwargs.pop('min_value', None)
|
|
super(IntegerField, self).__init__(**kwargs)
|
|
if self.max_value is not None:
|
|
message = self.error_messages['max_value'].format(max_value=self.max_value)
|
|
self.validators.append(MaxValueValidator(self.max_value, message=message))
|
|
if self.min_value is not None:
|
|
message = self.error_messages['min_value'].format(min_value=self.min_value)
|
|
self.validators.append(MinValueValidator(self.min_value, message=message))
|
|
|
|
def to_internal_value(self, data):
|
|
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
|
|
self.fail('max_string_length')
|
|
|
|
try:
|
|
data = int(self.re_decimal.sub('', str(data)))
|
|
except (ValueError, TypeError):
|
|
self.fail('invalid')
|
|
return data
|
|
|
|
def to_representation(self, value):
|
|
return int(value)
|
|
|
|
|
|
class FloatField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('A valid number is required.'),
|
|
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
|
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
|
|
'max_string_length': _('String value too large.')
|
|
}
|
|
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
|
|
|
|
def __init__(self, **kwargs):
|
|
self.max_value = kwargs.pop('max_value', None)
|
|
self.min_value = kwargs.pop('min_value', None)
|
|
super(FloatField, self).__init__(**kwargs)
|
|
if self.max_value is not None:
|
|
message = self.error_messages['max_value'].format(max_value=self.max_value)
|
|
self.validators.append(MaxValueValidator(self.max_value, message=message))
|
|
if self.min_value is not None:
|
|
message = self.error_messages['min_value'].format(min_value=self.min_value)
|
|
self.validators.append(MinValueValidator(self.min_value, message=message))
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
|
|
self.fail('max_string_length')
|
|
|
|
try:
|
|
return float(data)
|
|
except (TypeError, ValueError):
|
|
self.fail('invalid')
|
|
|
|
def to_representation(self, value):
|
|
return float(value)
|
|
|
|
|
|
class DecimalField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('A valid number is required.'),
|
|
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
|
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
|
|
'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
|
|
'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
|
|
'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
|
|
'max_string_length': _('String value too large.')
|
|
}
|
|
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
|
|
|
|
def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None,
|
|
localize=False, **kwargs):
|
|
self.max_digits = max_digits
|
|
self.decimal_places = decimal_places
|
|
self.localize = localize
|
|
if coerce_to_string is not None:
|
|
self.coerce_to_string = coerce_to_string
|
|
if self.localize:
|
|
self.coerce_to_string = True
|
|
|
|
self.max_value = max_value
|
|
self.min_value = min_value
|
|
|
|
if self.max_digits is not None and self.decimal_places is not None:
|
|
self.max_whole_digits = self.max_digits - self.decimal_places
|
|
else:
|
|
self.max_whole_digits = None
|
|
|
|
super(DecimalField, self).__init__(**kwargs)
|
|
|
|
if self.max_value is not None:
|
|
message = self.error_messages['max_value'].format(max_value=self.max_value)
|
|
self.validators.append(MaxValueValidator(self.max_value, message=message))
|
|
if self.min_value is not None:
|
|
message = self.error_messages['min_value'].format(min_value=self.min_value)
|
|
self.validators.append(MinValueValidator(self.min_value, message=message))
|
|
|
|
def to_internal_value(self, data):
|
|
"""
|
|
Validate that the input is a decimal number and return a Decimal
|
|
instance.
|
|
"""
|
|
|
|
data = smart_text(data).strip()
|
|
|
|
if self.localize:
|
|
data = sanitize_separators(data)
|
|
|
|
if len(data) > self.MAX_STRING_LENGTH:
|
|
self.fail('max_string_length')
|
|
|
|
try:
|
|
value = decimal.Decimal(data)
|
|
except decimal.DecimalException:
|
|
self.fail('invalid')
|
|
|
|
# Check for NaN. It is the only value that isn't equal to itself,
|
|
# so we can use this to identify NaN values.
|
|
if value != value:
|
|
self.fail('invalid')
|
|
|
|
# Check for infinity and negative infinity.
|
|
if value in (decimal.Decimal('Inf'), decimal.Decimal('-Inf')):
|
|
self.fail('invalid')
|
|
|
|
return self.quantize(self.validate_precision(value))
|
|
|
|
def validate_precision(self, value):
|
|
"""
|
|
Ensure that there are no more than max_digits in the number, and no
|
|
more than decimal_places digits after the decimal point.
|
|
|
|
Override this method to disable the precision validation for input
|
|
values or to enhance it in any way you need to.
|
|
"""
|
|
sign, digittuple, exponent = value.as_tuple()
|
|
|
|
if exponent >= 0:
|
|
# 1234500.0
|
|
total_digits = len(digittuple) + exponent
|
|
whole_digits = total_digits
|
|
decimal_places = 0
|
|
elif len(digittuple) > abs(exponent):
|
|
# 123.45
|
|
total_digits = len(digittuple)
|
|
whole_digits = total_digits - abs(exponent)
|
|
decimal_places = abs(exponent)
|
|
else:
|
|
# 0.001234
|
|
total_digits = abs(exponent)
|
|
whole_digits = 0
|
|
decimal_places = total_digits
|
|
|
|
if self.max_digits is not None and total_digits > self.max_digits:
|
|
self.fail('max_digits', max_digits=self.max_digits)
|
|
if self.decimal_places is not None and decimal_places > self.decimal_places:
|
|
self.fail('max_decimal_places', max_decimal_places=self.decimal_places)
|
|
if self.max_whole_digits is not None and whole_digits > self.max_whole_digits:
|
|
self.fail('max_whole_digits', max_whole_digits=self.max_whole_digits)
|
|
|
|
return value
|
|
|
|
def to_representation(self, value):
|
|
coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING)
|
|
|
|
if not isinstance(value, decimal.Decimal):
|
|
value = decimal.Decimal(six.text_type(value).strip())
|
|
|
|
quantized = self.quantize(value)
|
|
|
|
if not coerce_to_string:
|
|
return quantized
|
|
if self.localize:
|
|
return localize_input(quantized)
|
|
|
|
return '{0:f}'.format(quantized)
|
|
|
|
def quantize(self, value):
|
|
"""
|
|
Quantize the decimal value to the configured precision.
|
|
"""
|
|
if self.decimal_places is None:
|
|
return value
|
|
|
|
context = decimal.getcontext().copy()
|
|
if self.max_digits is not None:
|
|
context.prec = self.max_digits
|
|
return value.quantize(
|
|
decimal.Decimal('.1') ** self.decimal_places,
|
|
context=context
|
|
)
|
|
|
|
|
|
# Date & time fields...
|
|
|
|
class DateTimeField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
|
|
'date': _('Expected a datetime but got a date.'),
|
|
}
|
|
datetime_parser = datetime.datetime.strptime
|
|
|
|
def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs):
|
|
if format is not empty:
|
|
self.format = format
|
|
if input_formats is not None:
|
|
self.input_formats = input_formats
|
|
if default_timezone is not None:
|
|
self.timezone = default_timezone
|
|
super(DateTimeField, self).__init__(*args, **kwargs)
|
|
|
|
def enforce_timezone(self, value):
|
|
"""
|
|
When `self.default_timezone` is `None`, always return naive datetimes.
|
|
When `self.default_timezone` is not `None`, always return aware datetimes.
|
|
"""
|
|
field_timezone = getattr(self, 'timezone', self.default_timezone())
|
|
|
|
if (field_timezone is not None) and not timezone.is_aware(value):
|
|
return timezone.make_aware(value, field_timezone)
|
|
elif (field_timezone is None) and timezone.is_aware(value):
|
|
return timezone.make_naive(value, timezone.UTC())
|
|
return value
|
|
|
|
def default_timezone(self):
|
|
return timezone.get_default_timezone() if settings.USE_TZ else None
|
|
|
|
def to_internal_value(self, value):
|
|
input_formats = getattr(self, 'input_formats', api_settings.DATETIME_INPUT_FORMATS)
|
|
|
|
if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime):
|
|
self.fail('date')
|
|
|
|
if isinstance(value, datetime.datetime):
|
|
return self.enforce_timezone(value)
|
|
|
|
for input_format in input_formats:
|
|
if input_format.lower() == ISO_8601:
|
|
try:
|
|
parsed = parse_datetime(value)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
else:
|
|
if parsed is not None:
|
|
return self.enforce_timezone(parsed)
|
|
else:
|
|
try:
|
|
parsed = self.datetime_parser(value, input_format)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
else:
|
|
return self.enforce_timezone(parsed)
|
|
|
|
humanized_format = humanize_datetime.datetime_formats(input_formats)
|
|
self.fail('invalid', format=humanized_format)
|
|
|
|
def to_representation(self, value):
|
|
if not value:
|
|
return None
|
|
|
|
output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT)
|
|
|
|
if output_format is None or isinstance(value, six.string_types):
|
|
return value
|
|
|
|
if output_format.lower() == ISO_8601:
|
|
value = value.isoformat()
|
|
if value.endswith('+00:00'):
|
|
value = value[:-6] + 'Z'
|
|
return value
|
|
return value.strftime(output_format)
|
|
|
|
|
|
class DateField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
|
|
'datetime': _('Expected a date but got a datetime.'),
|
|
}
|
|
datetime_parser = datetime.datetime.strptime
|
|
|
|
def __init__(self, format=empty, input_formats=None, *args, **kwargs):
|
|
if format is not empty:
|
|
self.format = format
|
|
if input_formats is not None:
|
|
self.input_formats = input_formats
|
|
super(DateField, self).__init__(*args, **kwargs)
|
|
|
|
def to_internal_value(self, value):
|
|
input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
|
|
|
|
if isinstance(value, datetime.datetime):
|
|
self.fail('datetime')
|
|
|
|
if isinstance(value, datetime.date):
|
|
return value
|
|
|
|
for input_format in input_formats:
|
|
if input_format.lower() == ISO_8601:
|
|
try:
|
|
parsed = parse_date(value)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
else:
|
|
if parsed is not None:
|
|
return parsed
|
|
else:
|
|
try:
|
|
parsed = self.datetime_parser(value, input_format)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
else:
|
|
return parsed.date()
|
|
|
|
humanized_format = humanize_datetime.date_formats(input_formats)
|
|
self.fail('invalid', format=humanized_format)
|
|
|
|
def to_representation(self, value):
|
|
if not value:
|
|
return None
|
|
|
|
output_format = getattr(self, 'format', api_settings.DATE_FORMAT)
|
|
|
|
if output_format is None or isinstance(value, six.string_types):
|
|
return value
|
|
|
|
# Applying a `DateField` to a datetime value is almost always
|
|
# not a sensible thing to do, as it means naively dropping
|
|
# any explicit or implicit timezone info.
|
|
assert not isinstance(value, datetime.datetime), (
|
|
'Expected a `date`, but got a `datetime`. Refusing to coerce, '
|
|
'as this may mean losing timezone information. Use a custom '
|
|
'read-only field and deal with timezone issues explicitly.'
|
|
)
|
|
|
|
if output_format.lower() == ISO_8601:
|
|
return value.isoformat()
|
|
|
|
return value.strftime(output_format)
|
|
|
|
|
|
class TimeField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
|
|
}
|
|
datetime_parser = datetime.datetime.strptime
|
|
|
|
def __init__(self, format=empty, input_formats=None, *args, **kwargs):
|
|
if format is not empty:
|
|
self.format = format
|
|
if input_formats is not None:
|
|
self.input_formats = input_formats
|
|
super(TimeField, self).__init__(*args, **kwargs)
|
|
|
|
def to_internal_value(self, value):
|
|
input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
|
|
|
|
if isinstance(value, datetime.time):
|
|
return value
|
|
|
|
for input_format in input_formats:
|
|
if input_format.lower() == ISO_8601:
|
|
try:
|
|
parsed = parse_time(value)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
else:
|
|
if parsed is not None:
|
|
return parsed
|
|
else:
|
|
try:
|
|
parsed = self.datetime_parser(value, input_format)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
else:
|
|
return parsed.time()
|
|
|
|
humanized_format = humanize_datetime.time_formats(input_formats)
|
|
self.fail('invalid', format=humanized_format)
|
|
|
|
def to_representation(self, value):
|
|
if value in (None, ''):
|
|
return None
|
|
|
|
output_format = getattr(self, 'format', api_settings.TIME_FORMAT)
|
|
|
|
if output_format is None or isinstance(value, six.string_types):
|
|
return value
|
|
|
|
# Applying a `TimeField` to a datetime value is almost always
|
|
# not a sensible thing to do, as it means naively dropping
|
|
# any explicit or implicit timezone info.
|
|
assert not isinstance(value, datetime.datetime), (
|
|
'Expected a `time`, but got a `datetime`. Refusing to coerce, '
|
|
'as this may mean losing timezone information. Use a custom '
|
|
'read-only field and deal with timezone issues explicitly.'
|
|
)
|
|
|
|
if output_format.lower() == ISO_8601:
|
|
return value.isoformat()
|
|
return value.strftime(output_format)
|
|
|
|
|
|
class DurationField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
|
|
}
|
|
|
|
def to_internal_value(self, value):
|
|
if isinstance(value, datetime.timedelta):
|
|
return value
|
|
parsed = parse_duration(six.text_type(value))
|
|
if parsed is not None:
|
|
return parsed
|
|
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
|
|
|
|
def to_representation(self, value):
|
|
return duration_string(value)
|
|
|
|
|
|
# Choice types...
|
|
|
|
class ChoiceField(Field):
|
|
default_error_messages = {
|
|
'invalid_choice': _('"{input}" is not a valid choice.')
|
|
}
|
|
html_cutoff = None
|
|
html_cutoff_text = _('More than {count} items...')
|
|
|
|
def __init__(self, choices, **kwargs):
|
|
self.grouped_choices = to_choices_dict(choices)
|
|
self.choices = flatten_choices_dict(self.grouped_choices)
|
|
self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
|
|
self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
|
|
|
|
# Map the string representation of choices to the underlying value.
|
|
# Allows us to deal with eg. integer choices while supporting either
|
|
# integer or string input, but still get the correct datatype out.
|
|
self.choice_strings_to_values = {
|
|
six.text_type(key): key for key in self.choices.keys()
|
|
}
|
|
|
|
self.allow_blank = kwargs.pop('allow_blank', False)
|
|
|
|
super(ChoiceField, self).__init__(**kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
if data == '' and self.allow_blank:
|
|
return ''
|
|
|
|
try:
|
|
return self.choice_strings_to_values[six.text_type(data)]
|
|
except KeyError:
|
|
self.fail('invalid_choice', input=data)
|
|
|
|
def to_representation(self, value):
|
|
if value in ('', None):
|
|
return value
|
|
return self.choice_strings_to_values.get(six.text_type(value), value)
|
|
|
|
def iter_options(self):
|
|
"""
|
|
Helper method for use with templates rendering select widgets.
|
|
"""
|
|
return iter_options(
|
|
self.grouped_choices,
|
|
cutoff=self.html_cutoff,
|
|
cutoff_text=self.html_cutoff_text
|
|
)
|
|
|
|
|
|
class MultipleChoiceField(ChoiceField):
|
|
default_error_messages = {
|
|
'invalid_choice': _('"{input}" is not a valid choice.'),
|
|
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
|
|
'empty': _('This selection may not be empty.')
|
|
}
|
|
default_empty_html = []
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.allow_empty = kwargs.pop('allow_empty', True)
|
|
super(MultipleChoiceField, self).__init__(*args, **kwargs)
|
|
|
|
def get_value(self, dictionary):
|
|
if self.field_name not in dictionary:
|
|
if getattr(self.root, 'partial', False):
|
|
return empty
|
|
# We override the default field access in order to support
|
|
# lists in HTML forms.
|
|
if html.is_html_input(dictionary):
|
|
return dictionary.getlist(self.field_name)
|
|
return dictionary.get(self.field_name, empty)
|
|
|
|
def to_internal_value(self, data):
|
|
if isinstance(data, type('')) or not hasattr(data, '__iter__'):
|
|
self.fail('not_a_list', input_type=type(data).__name__)
|
|
if not self.allow_empty and len(data) == 0:
|
|
self.fail('empty')
|
|
|
|
return {
|
|
super(MultipleChoiceField, self).to_internal_value(item)
|
|
for item in data
|
|
}
|
|
|
|
def to_representation(self, value):
|
|
return {
|
|
self.choice_strings_to_values.get(six.text_type(item), item) for item in value
|
|
}
|
|
|
|
|
|
class FilePathField(ChoiceField):
|
|
default_error_messages = {
|
|
'invalid_choice': _('"{input}" is not a valid path choice.')
|
|
}
|
|
|
|
def __init__(self, path, match=None, recursive=False, allow_files=True,
|
|
allow_folders=False, required=None, **kwargs):
|
|
# Defer to Django's FilePathField implementation to get the
|
|
# valid set of choices.
|
|
field = DjangoFilePathField(
|
|
path, match=match, recursive=recursive, allow_files=allow_files,
|
|
allow_folders=allow_folders, required=required
|
|
)
|
|
kwargs['choices'] = field.choices
|
|
super(FilePathField, self).__init__(**kwargs)
|
|
|
|
|
|
# File types...
|
|
|
|
class FileField(Field):
|
|
default_error_messages = {
|
|
'required': _('No file was submitted.'),
|
|
'invalid': _('The submitted data was not a file. Check the encoding type on the form.'),
|
|
'no_name': _('No filename could be determined.'),
|
|
'empty': _('The submitted file is empty.'),
|
|
'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.max_length = kwargs.pop('max_length', None)
|
|
self.allow_empty_file = kwargs.pop('allow_empty_file', False)
|
|
if 'use_url' in kwargs:
|
|
self.use_url = kwargs.pop('use_url')
|
|
super(FileField, self).__init__(*args, **kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
try:
|
|
# `UploadedFile` objects should have name and size attributes.
|
|
file_name = data.name
|
|
file_size = data.size
|
|
except AttributeError:
|
|
self.fail('invalid')
|
|
|
|
if not file_name:
|
|
self.fail('no_name')
|
|
if not self.allow_empty_file and not file_size:
|
|
self.fail('empty')
|
|
if self.max_length and len(file_name) > self.max_length:
|
|
self.fail('max_length', max_length=self.max_length, length=len(file_name))
|
|
|
|
return data
|
|
|
|
def to_representation(self, value):
|
|
use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
|
|
|
|
if not value:
|
|
return None
|
|
|
|
if use_url:
|
|
if not getattr(value, 'url', None):
|
|
# If the file has not been saved it may not have a URL.
|
|
return None
|
|
url = value.url
|
|
request = self.context.get('request', None)
|
|
if request is not None:
|
|
return request.build_absolute_uri(url)
|
|
return url
|
|
return value.name
|
|
|
|
|
|
class ImageField(FileField):
|
|
default_error_messages = {
|
|
'invalid_image': _(
|
|
'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
|
|
),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
|
|
super(ImageField, self).__init__(*args, **kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
# Image validation is a bit grungy, so we'll just outright
|
|
# defer to Django's implementation so we don't need to
|
|
# consider it, or treat PIL as a test dependency.
|
|
file_object = super(ImageField, self).to_internal_value(data)
|
|
django_field = self._DjangoImageField()
|
|
django_field.error_messages = self.error_messages
|
|
django_field.to_python(file_object)
|
|
return file_object
|
|
|
|
|
|
# Composite field types...
|
|
|
|
class _UnvalidatedField(Field):
|
|
def __init__(self, *args, **kwargs):
|
|
super(_UnvalidatedField, self).__init__(*args, **kwargs)
|
|
self.allow_blank = True
|
|
self.allow_null = True
|
|
|
|
def to_internal_value(self, data):
|
|
return data
|
|
|
|
def to_representation(self, value):
|
|
return value
|
|
|
|
|
|
class ListField(Field):
|
|
child = _UnvalidatedField()
|
|
initial = []
|
|
default_error_messages = {
|
|
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
|
|
'empty': _('This list may not be empty.')
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.child = kwargs.pop('child', copy.deepcopy(self.child))
|
|
self.allow_empty = kwargs.pop('allow_empty', True)
|
|
|
|
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
|
|
assert self.child.source is None, (
|
|
"The `source` argument is not meaningful when applied to a `child=` field. "
|
|
"Remove `source=` from the field declaration."
|
|
)
|
|
|
|
super(ListField, self).__init__(*args, **kwargs)
|
|
self.child.bind(field_name='', parent=self)
|
|
|
|
def get_value(self, dictionary):
|
|
if self.field_name not in dictionary:
|
|
if getattr(self.root, 'partial', False):
|
|
return empty
|
|
# We override the default field access in order to support
|
|
# lists in HTML forms.
|
|
if html.is_html_input(dictionary):
|
|
val = dictionary.getlist(self.field_name, [])
|
|
if len(val) > 0:
|
|
# Support QueryDict lists in HTML input.
|
|
return val
|
|
return html.parse_html_list(dictionary, prefix=self.field_name)
|
|
return dictionary.get(self.field_name, empty)
|
|
|
|
def to_internal_value(self, data):
|
|
"""
|
|
List of dicts of native values <- List of dicts of primitive datatypes.
|
|
"""
|
|
if html.is_html_input(data):
|
|
data = html.parse_html_list(data)
|
|
if isinstance(data, type('')) or isinstance(data, collections.Mapping) or not hasattr(data, '__iter__'):
|
|
self.fail('not_a_list', input_type=type(data).__name__)
|
|
if not self.allow_empty and len(data) == 0:
|
|
self.fail('empty')
|
|
return [self.child.run_validation(item) for item in data]
|
|
|
|
def to_representation(self, data):
|
|
"""
|
|
List of object instances -> List of dicts of primitive datatypes.
|
|
"""
|
|
return [self.child.to_representation(item) if item is not None else None for item in data]
|
|
|
|
|
|
class DictField(Field):
|
|
child = _UnvalidatedField()
|
|
initial = {}
|
|
default_error_messages = {
|
|
'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".')
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.child = kwargs.pop('child', copy.deepcopy(self.child))
|
|
|
|
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
|
|
assert self.child.source is None, (
|
|
"The `source` argument is not meaningful when applied to a `child=` field. "
|
|
"Remove `source=` from the field declaration."
|
|
)
|
|
|
|
super(DictField, self).__init__(*args, **kwargs)
|
|
self.child.bind(field_name='', parent=self)
|
|
|
|
def get_value(self, dictionary):
|
|
# We override the default field access in order to support
|
|
# dictionaries in HTML forms.
|
|
if html.is_html_input(dictionary):
|
|
return html.parse_html_dict(dictionary, prefix=self.field_name)
|
|
return dictionary.get(self.field_name, empty)
|
|
|
|
def to_internal_value(self, data):
|
|
"""
|
|
Dicts of native values <- Dicts of primitive datatypes.
|
|
"""
|
|
if html.is_html_input(data):
|
|
data = html.parse_html_dict(data)
|
|
if not isinstance(data, dict):
|
|
self.fail('not_a_dict', input_type=type(data).__name__)
|
|
return {
|
|
six.text_type(key): self.child.run_validation(value)
|
|
for key, value in data.items()
|
|
}
|
|
|
|
def to_representation(self, value):
|
|
"""
|
|
List of object instances -> List of dicts of primitive datatypes.
|
|
"""
|
|
return {
|
|
six.text_type(key): self.child.to_representation(val) if val is not None else None
|
|
for key, val in value.items()
|
|
}
|
|
|
|
|
|
class JSONField(Field):
|
|
default_error_messages = {
|
|
'invalid': _('Value must be valid JSON.')
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.binary = kwargs.pop('binary', False)
|
|
super(JSONField, self).__init__(*args, **kwargs)
|
|
|
|
def to_internal_value(self, data):
|
|
try:
|
|
if self.binary:
|
|
if isinstance(data, six.binary_type):
|
|
data = data.decode('utf-8')
|
|
return json.loads(data)
|
|
else:
|
|
json.dumps(data)
|
|
except (TypeError, ValueError):
|
|
self.fail('invalid')
|
|
return data
|
|
|
|
def to_representation(self, value):
|
|
if self.binary:
|
|
value = json.dumps(value)
|
|
# On python 2.x the return type for json.dumps() is underspecified.
|
|
# On python 3.x json.dumps() returns unicode strings.
|
|
if isinstance(value, six.text_type):
|
|
value = bytes(value.encode('utf-8'))
|
|
return value
|
|
|
|
|
|
# Miscellaneous field types...
|
|
|
|
class ReadOnlyField(Field):
|
|
"""
|
|
A read-only field that simply returns the field value.
|
|
|
|
If the field is a method with no parameters, the method will be called
|
|
and it's return value used as the representation.
|
|
|
|
For example, the following would call `get_expiry_date()` on the object:
|
|
|
|
class ExampleSerializer(Serializer):
|
|
expiry_date = ReadOnlyField(source='get_expiry_date')
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
kwargs['read_only'] = True
|
|
super(ReadOnlyField, self).__init__(**kwargs)
|
|
|
|
def to_representation(self, value):
|
|
return value
|
|
|
|
|
|
class HiddenField(Field):
|
|
"""
|
|
A hidden field does not take input from the user, or present any output,
|
|
but it does populate a field in `validated_data`, based on its default
|
|
value. This is particularly useful when we have a `unique_for_date`
|
|
constraint on a pair of fields, as we need some way to include the date in
|
|
the validated data.
|
|
"""
|
|
def __init__(self, **kwargs):
|
|
assert 'default' in kwargs, 'default is a required argument.'
|
|
kwargs['write_only'] = True
|
|
super(HiddenField, self).__init__(**kwargs)
|
|
|
|
def get_value(self, dictionary):
|
|
# We always use the default value for `HiddenField`.
|
|
# User input is never provided or accepted.
|
|
return empty
|
|
|
|
def to_internal_value(self, data):
|
|
return data
|
|
|
|
|
|
class SerializerMethodField(Field):
|
|
"""
|
|
A read-only field that get its representation from calling a method on the
|
|
parent serializer class. The method called will be of the form
|
|
"get_{field_name}", and should take a single argument, which is the
|
|
object being serialized.
|
|
|
|
For example:
|
|
|
|
class ExampleSerializer(self):
|
|
extra_info = SerializerMethodField()
|
|
|
|
def get_extra_info(self, obj):
|
|
return ... # Calculate some data to return.
|
|
"""
|
|
def __init__(self, method_name=None, **kwargs):
|
|
self.method_name = method_name
|
|
kwargs['source'] = '*'
|
|
kwargs['read_only'] = True
|
|
super(SerializerMethodField, self).__init__(**kwargs)
|
|
|
|
def bind(self, field_name, parent):
|
|
# In order to enforce a consistent style, we error if a redundant
|
|
# 'method_name' argument has been used. For example:
|
|
# my_field = serializer.SerializerMethodField(method_name='get_my_field')
|
|
default_method_name = 'get_{field_name}'.format(field_name=field_name)
|
|
assert self.method_name != default_method_name, (
|
|
"It is redundant to specify `%s` on SerializerMethodField '%s' in "
|
|
"serializer '%s', because it is the same as the default method name. "
|
|
"Remove the `method_name` argument." %
|
|
(self.method_name, field_name, parent.__class__.__name__)
|
|
)
|
|
|
|
# The method name should default to `get_{field_name}`.
|
|
if self.method_name is None:
|
|
self.method_name = default_method_name
|
|
|
|
super(SerializerMethodField, self).bind(field_name, parent)
|
|
|
|
def to_representation(self, value):
|
|
method = getattr(self.parent, self.method_name)
|
|
return method(value)
|
|
|
|
|
|
class ModelField(Field):
|
|
"""
|
|
A generic field that can be used against an arbitrary model field.
|
|
|
|
This is used by `ModelSerializer` when dealing with custom model fields,
|
|
that do not have a serializer field to be mapped to.
|
|
"""
|
|
default_error_messages = {
|
|
'max_length': _('Ensure this field has no more than {max_length} characters.'),
|
|
}
|
|
|
|
def __init__(self, model_field, **kwargs):
|
|
self.model_field = model_field
|
|
# The `max_length` option is supported by Django's base `Field` class,
|
|
# so we'd better support it here.
|
|
max_length = kwargs.pop('max_length', None)
|
|
super(ModelField, self).__init__(**kwargs)
|
|
if max_length is not None:
|
|
message = self.error_messages['max_length'].format(max_length=max_length)
|
|
self.validators.append(MaxLengthValidator(max_length, message=message))
|
|
|
|
def to_internal_value(self, data):
|
|
rel = get_remote_field(self.model_field, default=None)
|
|
if rel is not None:
|
|
return rel.to._meta.get_field(rel.field_name).to_python(data)
|
|
return self.model_field.to_python(data)
|
|
|
|
def get_attribute(self, obj):
|
|
# We pass the object instance onto `to_representation`,
|
|
# not just the field attribute.
|
|
return obj
|
|
|
|
def to_representation(self, obj):
|
|
value = value_from_object(self.model_field, obj)
|
|
if is_protected_type(value):
|
|
return value
|
|
return self.model_field.value_to_string(obj)
|