mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	* Allow format duration as ISO-8601 * Update tests/test_fields.py Co-authored-by: Bruno Alla <browniebroke@users.noreply.github.com> * Update tests/test_fields.py Co-authored-by: Bruno Alla <browniebroke@users.noreply.github.com> * Add validation for DurationField format, add more tests for it and improve related docs * Add more precise validation check for duration field format and adjust docs * Adjust typo in duration field docs --------- Co-authored-by: Asif Saif Uddin <auvipy@gmail.com> Co-authored-by: Bruno Alla <browniebroke@users.noreply.github.com>
		
			
				
	
	
		
			1943 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1943 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import contextlib
 | |
| import copy
 | |
| import datetime
 | |
| import decimal
 | |
| import functools
 | |
| import inspect
 | |
| import re
 | |
| import uuid
 | |
| import warnings
 | |
| from collections.abc import Mapping
 | |
| from enum import Enum
 | |
| 
 | |
| from django.conf import settings
 | |
| from django.core.exceptions import ObjectDoesNotExist
 | |
| from django.core.exceptions import ValidationError as DjangoValidationError
 | |
| from django.core.validators import (
 | |
|     EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
 | |
|     MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
 | |
|     URLValidator
 | |
| )
 | |
| from django.forms import FilePathField as DjangoFilePathField
 | |
| from django.forms import ImageField as DjangoImageField
 | |
| from django.utils import timezone
 | |
| from django.utils.dateparse import (
 | |
|     parse_date, parse_datetime, parse_duration, parse_time
 | |
| )
 | |
| from django.utils.duration import duration_iso_string, duration_string
 | |
| from django.utils.encoding import is_protected_type, smart_str
 | |
| from django.utils.formats import localize_input, sanitize_separators
 | |
| from django.utils.ipv6 import clean_ipv6_address
 | |
| from django.utils.translation import gettext_lazy as _
 | |
| 
 | |
| try:
 | |
|     import pytz
 | |
| except ImportError:
 | |
|     pytz = None
 | |
| 
 | |
| from rest_framework import DJANGO_DURATION_FORMAT, ISO_8601
 | |
| from rest_framework.compat import ip_address_validators
 | |
| from rest_framework.exceptions import ErrorDetail, ValidationError
 | |
| from rest_framework.settings import api_settings
 | |
| from rest_framework.utils import html, humanize_datetime, json, representation
 | |
| from rest_framework.utils.formatting import lazy_format
 | |
| from rest_framework.utils.timezone import valid_datetime
 | |
| from rest_framework.validators import ProhibitSurrogateCharactersValidator
 | |
| 
 | |
| 
 | |
| 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
 | |
| 
 | |
| 
 | |
| class BuiltinSignatureError(Exception):
 | |
|     """
 | |
|     Built-in function signatures are not inspectable. This exception is raised
 | |
|     so the serializer can raise a helpful error message.
 | |
|     """
 | |
|     pass
 | |
| 
 | |
| 
 | |
| def is_simple_callable(obj):
 | |
|     """
 | |
|     True if the object is a callable that takes no arguments.
 | |
|     """
 | |
|     if not callable(obj):
 | |
|         return False
 | |
| 
 | |
|     # Bail early since we cannot inspect built-in function signatures.
 | |
|     if inspect.isbuiltin(obj):
 | |
|         raise BuiltinSignatureError(
 | |
|             'Built-in function signatures are not inspectable. '
 | |
|             'Wrap the function call in a simple, pure Python function.')
 | |
| 
 | |
|     if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
 | |
|         return False
 | |
| 
 | |
|     sig = inspect.signature(obj)
 | |
|     params = sig.parameters.values()
 | |
|     return all(
 | |
|         param.kind == param.VAR_POSITIONAL or
 | |
|         param.kind == param.VAR_KEYWORD or
 | |
|         param.default != param.empty
 | |
|         for param in params
 | |
|     )
 | |
| 
 | |
| 
 | |
| 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:
 | |
|         try:
 | |
|             if isinstance(instance, 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(f'Exception raised in callable attribute "{attr}"; original exception was: {exc}')
 | |
| 
 | |
|     return instance
 | |
| 
 | |
| 
 | |
| 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: '2'}}
 | |
|     """
 | |
|     # 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 = {}
 | |
|     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 = {}
 | |
|     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:
 | |
|         start_option_group = True
 | |
|         end_option_group = False
 | |
| 
 | |
|         def __init__(self, label):
 | |
|             self.label = label
 | |
| 
 | |
|     class EndOptionGroup:
 | |
|         start_option_group = False
 | |
|         end_option_group = True
 | |
| 
 | |
|     class Option:
 | |
|         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)
 | |
| 
 | |
| 
 | |
| def get_error_detail(exc_info):
 | |
|     """
 | |
|     Given a Django ValidationError, return a list of ErrorDetail,
 | |
|     with the `code` populated.
 | |
|     """
 | |
|     code = getattr(exc_info, 'code', None) or 'invalid'
 | |
| 
 | |
|     try:
 | |
|         error_dict = exc_info.error_dict
 | |
|     except AttributeError:
 | |
|         return [
 | |
|             ErrorDetail((error.message % error.params) if error.params else error.message,
 | |
|                         code=error.code if error.code else code)
 | |
|             for error in exc_info.error_list]
 | |
|     return {
 | |
|         k: [
 | |
|             ErrorDetail((error.message % error.params) if error.params else error.message,
 | |
|                         code=error.code if error.code else code)
 | |
|             for error in errors
 | |
|         ] for k, errors in error_dict.items()
 | |
|     }
 | |
| 
 | |
| 
 | |
| class CreateOnlyDefault:
 | |
|     """
 | |
|     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.
 | |
|     """
 | |
|     requires_context = True
 | |
| 
 | |
|     def __init__(self, default):
 | |
|         self.default = default
 | |
| 
 | |
|     def __call__(self, serializer_field):
 | |
|         is_update = serializer_field.parent.instance is not None
 | |
|         if is_update:
 | |
|             raise SkipField()
 | |
|         if callable(self.default):
 | |
|             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:
 | |
|     requires_context = True
 | |
| 
 | |
|     def __call__(self, serializer_field):
 | |
|         return serializer_field.context['request'].user
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return '%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:
 | |
|     _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 = list(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
 | |
| 
 | |
|     # Allow generic typing checking for fields.
 | |
|     def __class_getitem__(cls, *args, **kwargs):
 | |
|         return cls
 | |
| 
 | |
|     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 list(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 BuiltinSignatureError as exc:
 | |
|             msg = (
 | |
|                 'Field source for `{serializer}.{field}` maps to a built-in '
 | |
|                 'function type and is invalid. Define a property or method on '
 | |
|                 'the `{instance}` instance that wraps the call to the built-in '
 | |
|                 'function.'.format(
 | |
|                     serializer=self.parent.__class__.__name__,
 | |
|                     field=self.field_name,
 | |
|                     instance=instance.__class__.__name__,
 | |
|                 )
 | |
|             )
 | |
|             raise type(exc)(msg)
 | |
|         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)
 | |
| 
 | |
|     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 getattr(self.default, 'requires_context', False):
 | |
|                 return self.default(self)
 | |
|             else:
 | |
|                 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')
 | |
|             # Nullable `source='*'` fields should not be skipped when its named
 | |
|             # field is given a null value. This is because `source='*'` means
 | |
|             # the field is passed the entire object, which is not null.
 | |
|             elif self.source == '*':
 | |
|                 return (False, None)
 | |
|             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:
 | |
|             try:
 | |
|                 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
 | |
|                 # attempting to accumulate a list of errors.
 | |
|                 if isinstance(exc.detail, dict):
 | |
|                     raise
 | |
|                 errors.extend(exc.detail)
 | |
|             except DjangoValidationError as exc:
 | |
|                 errors.extend(get_error_detail(exc))
 | |
|         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 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 to_representation(self, value):
 | |
|         """
 | |
|         Transform the *outgoing* native value into primitive data.
 | |
|         """
 | |
|         raise NotImplementedError(
 | |
|             '{cls}.to_representation() must be implemented for field {field_name}.'.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, code=key)
 | |
| 
 | |
|     @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
 | |
| 
 | |
|     @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().__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/encode/django-rest-framework/issues/1954
 | |
|         # and https://github.com/encode/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, memo) 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 representation.field_repr(self)
 | |
| 
 | |
| 
 | |
| # Boolean types...
 | |
| 
 | |
| class BooleanField(Field):
 | |
|     default_error_messages = {
 | |
|         'invalid': _('Must be a valid boolean.')
 | |
|     }
 | |
|     default_empty_html = False
 | |
|     initial = False
 | |
|     TRUE_VALUES = {
 | |
|         't',
 | |
|         'y',
 | |
|         'yes',
 | |
|         'true',
 | |
|         'on',
 | |
|         '1',
 | |
|         1,
 | |
|         True,
 | |
|     }
 | |
|     FALSE_VALUES = {
 | |
|         'f',
 | |
|         'n',
 | |
|         'no',
 | |
|         'false',
 | |
|         'off',
 | |
|         '0',
 | |
|         0,
 | |
|         0.0,
 | |
|         False,
 | |
|     }
 | |
|     NULL_VALUES = {'null', '', None}
 | |
| 
 | |
|     def __init__(self, **kwargs):
 | |
|         if kwargs.get('allow_null', False):
 | |
|             self.default_empty_html = None
 | |
|             self.initial = None
 | |
|         super().__init__(**kwargs)
 | |
| 
 | |
|     @staticmethod
 | |
|     def _lower_if_str(value):
 | |
|         if isinstance(value, str):
 | |
|             return value.lower()
 | |
|         return value
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         with contextlib.suppress(TypeError):
 | |
|             if self._lower_if_str(data) in self.TRUE_VALUES:
 | |
|                 return True
 | |
|             elif self._lower_if_str(data) in self.FALSE_VALUES:
 | |
|                 return False
 | |
|             elif self._lower_if_str(data) in self.NULL_VALUES and self.allow_null:
 | |
|                 return None
 | |
|         self.fail("invalid", input=data)
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         if self._lower_if_str(value) in self.TRUE_VALUES:
 | |
|             return True
 | |
|         elif self._lower_if_str(value) in self.FALSE_VALUES:
 | |
|             return False
 | |
|         if self._lower_if_str(value) in self.NULL_VALUES and self.allow_null:
 | |
|             return None
 | |
|         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().__init__(**kwargs)
 | |
|         if self.max_length is not None:
 | |
|             message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
 | |
|             self.validators.append(
 | |
|                 MaxLengthValidator(self.max_length, message=message))
 | |
|         if self.min_length is not None:
 | |
|             message = lazy_format(self.error_messages['min_length'], min_length=self.min_length)
 | |
|             self.validators.append(
 | |
|                 MinLengthValidator(self.min_length, message=message))
 | |
| 
 | |
|         self.validators.append(ProhibitNullCharactersValidator())
 | |
|         self.validators.append(ProhibitSurrogateCharactersValidator())
 | |
| 
 | |
|     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 str(data).strip() == ''):
 | |
|             if not self.allow_blank:
 | |
|                 self.fail('blank')
 | |
|             return ''
 | |
|         return super().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, (str, int, float,)):
 | |
|             self.fail('invalid')
 | |
|         value = str(data)
 | |
|         return value.strip() if self.trim_whitespace else value
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         return str(value)
 | |
| 
 | |
| 
 | |
| class EmailField(CharField):
 | |
|     default_error_messages = {
 | |
|         'invalid': _('Enter a valid email address.')
 | |
|     }
 | |
| 
 | |
|     def __init__(self, **kwargs):
 | |
|         super().__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().__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.'),
 | |
|         'invalid_unicode': _('Enter a valid "slug" consisting of Unicode letters, numbers, underscores, or hyphens.')
 | |
|     }
 | |
| 
 | |
|     def __init__(self, allow_unicode=False, **kwargs):
 | |
|         super().__init__(**kwargs)
 | |
|         self.allow_unicode = allow_unicode
 | |
|         if self.allow_unicode:
 | |
|             validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode'])
 | |
|         else:
 | |
|             validator = RegexValidator(re.compile(r'^[-a-zA-Z0-9_]+$'), 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().__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': _('Must be 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 "{}"'.format('", "'.join(self.valid_formats))
 | |
|             )
 | |
|         super().__init__(**kwargs)
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         if not isinstance(data, uuid.UUID):
 | |
|             try:
 | |
|                 if isinstance(data, int):
 | |
|                     return uuid.UUID(int=data)
 | |
|                 elif isinstance(data, str):
 | |
|                     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().__init__(**kwargs)
 | |
|         validators = ip_address_validators(protocol, self.unpack_ipv4)
 | |
|         self.validators.extend(validators)
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         if not isinstance(data, str):
 | |
|             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().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().__init__(**kwargs)
 | |
|         if self.max_value is not None:
 | |
|             message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
 | |
|             self.validators.append(
 | |
|                 MaxValueValidator(self.max_value, message=message))
 | |
|         if self.min_value is not None:
 | |
|             message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
 | |
|             self.validators.append(
 | |
|                 MinValueValidator(self.min_value, message=message))
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         if isinstance(data, str) 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.'),
 | |
|         'overflow': _('Integer value too large to convert to float')
 | |
|     }
 | |
|     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().__init__(**kwargs)
 | |
|         if self.max_value is not None:
 | |
|             message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
 | |
|             self.validators.append(
 | |
|                 MaxValueValidator(self.max_value, message=message))
 | |
|         if self.min_value is not None:
 | |
|             message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
 | |
|             self.validators.append(
 | |
|                 MinValueValidator(self.min_value, message=message))
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
| 
 | |
|         if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH:
 | |
|             self.fail('max_string_length')
 | |
| 
 | |
|         try:
 | |
|             return float(data)
 | |
|         except (TypeError, ValueError):
 | |
|             self.fail('invalid')
 | |
|         except OverflowError:
 | |
|             self.fail('overflow')
 | |
| 
 | |
|     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, rounding=None, normalize_output=False, **kwargs):
 | |
|         self.max_digits = max_digits
 | |
|         self.decimal_places = decimal_places
 | |
|         self.localize = localize
 | |
|         self.normalize_output = normalize_output
 | |
|         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_value is not None and not isinstance(self.max_value, (int, decimal.Decimal)):
 | |
|             warnings.warn("max_value should be an integer or Decimal instance.")
 | |
|         if self.min_value is not None and not isinstance(self.min_value, (int, decimal.Decimal)):
 | |
|             warnings.warn("min_value should be an integer or Decimal instance.")
 | |
| 
 | |
|         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().__init__(**kwargs)
 | |
| 
 | |
|         if self.max_value is not None:
 | |
|             message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
 | |
|             self.validators.append(
 | |
|                 MaxValueValidator(self.max_value, message=message))
 | |
|         if self.min_value is not None:
 | |
|             message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
 | |
|             self.validators.append(
 | |
|                 MinValueValidator(self.min_value, message=message))
 | |
| 
 | |
|         if rounding is not None:
 | |
|             valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')]
 | |
|             assert rounding in valid_roundings, (
 | |
|                 'Invalid rounding option %s. Valid values for rounding are: %s' % (rounding, valid_roundings))
 | |
|         self.rounding = rounding
 | |
| 
 | |
|     def validate_empty_values(self, data):
 | |
|         if smart_str(data).strip() == '' and self.allow_null:
 | |
|             return (True, None)
 | |
|         return super().validate_empty_values(data)
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         """
 | |
|         Validate that the input is a decimal number and return a Decimal
 | |
|         instance.
 | |
|         """
 | |
| 
 | |
|         data = smart_str(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')
 | |
| 
 | |
|         if value.is_nan():
 | |
|             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 value is None:
 | |
|             if coerce_to_string:
 | |
|                 return ''
 | |
|             else:
 | |
|                 return None
 | |
| 
 | |
|         if not isinstance(value, decimal.Decimal):
 | |
|             value = decimal.Decimal(str(value).strip())
 | |
| 
 | |
|         quantized = self.quantize(value)
 | |
| 
 | |
|         if self.normalize_output:
 | |
|             quantized = quantized.normalize()
 | |
| 
 | |
|         if not coerce_to_string:
 | |
|             return quantized
 | |
|         if self.localize:
 | |
|             return localize_input(quantized)
 | |
| 
 | |
|         return f'{quantized:f}'
 | |
| 
 | |
|     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,
 | |
|             rounding=self.rounding,
 | |
|             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.'),
 | |
|         'make_aware': _('Invalid datetime for the timezone "{timezone}".'),
 | |
|         'overflow': _('Datetime value out of range.')
 | |
|     }
 | |
|     datetime_parser = datetime.datetime.strptime
 | |
| 
 | |
|     def __init__(self, format=empty, input_formats=None, default_timezone=None, **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().__init__(**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 = self.timezone if hasattr(self, 'timezone') else self.default_timezone()
 | |
| 
 | |
|         if field_timezone is not None:
 | |
|             if timezone.is_aware(value):
 | |
|                 try:
 | |
|                     return value.astimezone(field_timezone)
 | |
|                 except OverflowError:
 | |
|                     self.fail('overflow')
 | |
|             try:
 | |
|                 dt = timezone.make_aware(value, field_timezone)
 | |
|                 # When the resulting datetime is a ZoneInfo instance, it won't necessarily
 | |
|                 # throw given an invalid datetime, so we need to specifically check.
 | |
|                 if not valid_datetime(dt):
 | |
|                     self.fail('make_aware', timezone=field_timezone)
 | |
|                 return dt
 | |
|             except Exception as e:
 | |
|                 if pytz and isinstance(e, pytz.exceptions.InvalidTimeError):
 | |
|                     self.fail('make_aware', timezone=field_timezone)
 | |
|                 raise e
 | |
|         elif (field_timezone is None) and timezone.is_aware(value):
 | |
|             return timezone.make_naive(value, datetime.timezone.utc)
 | |
|         return value
 | |
| 
 | |
|     def default_timezone(self):
 | |
|         return timezone.get_current_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:
 | |
|             with contextlib.suppress(ValueError, TypeError):
 | |
|                 if input_format.lower() == ISO_8601:
 | |
|                     parsed = parse_datetime(value)
 | |
|                     if parsed is not None:
 | |
|                         return self.enforce_timezone(parsed)
 | |
| 
 | |
|                 parsed = self.datetime_parser(value, input_format)
 | |
|                 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, str):
 | |
|             return value
 | |
| 
 | |
|         value = self.enforce_timezone(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, **kwargs):
 | |
|         if format is not empty:
 | |
|             self.format = format
 | |
|         if input_formats is not None:
 | |
|             self.input_formats = input_formats
 | |
|         super().__init__(**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, str):
 | |
|             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, **kwargs):
 | |
|         if format is not empty:
 | |
|             self.format = format
 | |
|         if input_formats is not None:
 | |
|             self.input_formats = input_formats
 | |
|         super().__init__(**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, str):
 | |
|             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}.'),
 | |
|         '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}.'),
 | |
|         'overflow': _('The number of days must be between {min_days} and {max_days}.'),
 | |
|     }
 | |
| 
 | |
|     def __init__(self, *, format=empty, **kwargs):
 | |
|         self.max_value = kwargs.pop('max_value', None)
 | |
|         self.min_value = kwargs.pop('min_value', None)
 | |
|         if format is not empty:
 | |
|             if format is None or (isinstance(format, str) and format.lower() in (ISO_8601, DJANGO_DURATION_FORMAT)):
 | |
|                 self.format = format
 | |
|             elif isinstance(format, str):
 | |
|                 raise ValueError(
 | |
|                     f"Unknown duration format provided, got '{format}'"
 | |
|                     " while expecting 'django', 'iso-8601' or `None`."
 | |
|                 )
 | |
|             else:
 | |
|                 raise TypeError(
 | |
|                     "duration format must be either str or `None`,"
 | |
|                     f" not {type(format).__name__}"
 | |
|                 )
 | |
|         super().__init__(**kwargs)
 | |
|         if self.max_value is not None:
 | |
|             message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
 | |
|             self.validators.append(
 | |
|                 MaxValueValidator(self.max_value, message=message))
 | |
|         if self.min_value is not None:
 | |
|             message = lazy_format(self.error_messages['min_value'], min_value=self.min_value)
 | |
|             self.validators.append(
 | |
|                 MinValueValidator(self.min_value, message=message))
 | |
| 
 | |
|     def to_internal_value(self, value):
 | |
|         if isinstance(value, datetime.timedelta):
 | |
|             return value
 | |
|         try:
 | |
|             parsed = parse_duration(str(value))
 | |
|         except OverflowError:
 | |
|             self.fail('overflow', min_days=datetime.timedelta.min.days, max_days=datetime.timedelta.max.days)
 | |
|         if parsed is not None:
 | |
|             return parsed
 | |
|         self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         output_format = getattr(self, 'format', api_settings.DURATION_FORMAT)
 | |
| 
 | |
|         if output_format is None:
 | |
|             return value
 | |
| 
 | |
|         if isinstance(output_format, str):
 | |
|             if output_format.lower() == ISO_8601:
 | |
|                 return duration_iso_string(value)
 | |
| 
 | |
|             if output_format.lower() == DJANGO_DURATION_FORMAT:
 | |
|                 return duration_string(value)
 | |
| 
 | |
|             raise ValueError(
 | |
|                 f"Unknown duration format provided, got '{output_format}'"
 | |
|                 " while expecting 'django', 'iso-8601' or `None`."
 | |
|             )
 | |
|         raise TypeError(
 | |
|             "duration format must be either str or `None`,"
 | |
|             f" not {type(output_format).__name__}"
 | |
|         )
 | |
| 
 | |
| 
 | |
| # 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.choices = choices
 | |
|         self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
 | |
|         self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
 | |
| 
 | |
|         self.allow_blank = kwargs.pop('allow_blank', False)
 | |
| 
 | |
|         super().__init__(**kwargs)
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         if data == '' and self.allow_blank:
 | |
|             return ''
 | |
|         if isinstance(data, Enum) and str(data) != str(data.value):
 | |
|             data = data.value
 | |
|         try:
 | |
|             return self.choice_strings_to_values[str(data)]
 | |
|         except KeyError:
 | |
|             self.fail('invalid_choice', input=data)
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         if value in ('', None):
 | |
|             return value
 | |
|         if isinstance(value, Enum) and str(value) != str(value.value):
 | |
|             value = value.value
 | |
|         return self.choice_strings_to_values.get(str(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
 | |
|         )
 | |
| 
 | |
|     def _get_choices(self):
 | |
|         return self._choices
 | |
| 
 | |
|     def _set_choices(self, choices):
 | |
|         self.grouped_choices = to_choices_dict(choices)
 | |
|         self._choices = flatten_choices_dict(self.grouped_choices)
 | |
| 
 | |
|         # 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 = {
 | |
|             str(key.value) if isinstance(key, Enum) and str(key) != str(key.value) else str(key): key for key in self.choices
 | |
|         }
 | |
| 
 | |
|     choices = property(_get_choices, _set_choices)
 | |
| 
 | |
| 
 | |
| 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, **kwargs):
 | |
|         self.allow_empty = kwargs.pop('allow_empty', True)
 | |
|         super().__init__(**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, str) 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 {
 | |
|             # Arguments for super() are needed because of scoping inside
 | |
|             # comprehensions.
 | |
|             super(MultipleChoiceField, self).to_internal_value(item)
 | |
|             for item in data
 | |
|         }
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         return {
 | |
|             self.choice_strings_to_values.get(str(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
 | |
|         kwargs['required'] = required
 | |
|         super().__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, **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().__init__(**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):
 | |
|         if not value:
 | |
|             return None
 | |
| 
 | |
|         use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
 | |
|         if use_url:
 | |
|             try:
 | |
|                 url = value.url
 | |
|             except AttributeError:
 | |
|                 return None
 | |
|             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, **kwargs):
 | |
|         self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
 | |
|         super().__init__(**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().to_internal_value(data)
 | |
|         django_field = self._DjangoImageField()
 | |
|         django_field.error_messages = self.error_messages
 | |
|         return django_field.clean(file_object)
 | |
| 
 | |
| 
 | |
| # Composite field types...
 | |
| 
 | |
| class _UnvalidatedField(Field):
 | |
|     def __init__(self, **kwargs):
 | |
|         super().__init__(**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.'),
 | |
|         'min_length': _('Ensure this field has at least {min_length} elements.'),
 | |
|         'max_length': _('Ensure this field has no more than {max_length} elements.')
 | |
|     }
 | |
| 
 | |
|     def __init__(self, **kwargs):
 | |
|         self.child = kwargs.pop('child', copy.deepcopy(self.child))
 | |
|         self.allow_empty = kwargs.pop('allow_empty', True)
 | |
|         self.max_length = kwargs.pop('max_length', None)
 | |
|         self.min_length = kwargs.pop('min_length', None)
 | |
| 
 | |
|         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().__init__(**kwargs)
 | |
|         self.child.bind(field_name='', parent=self)
 | |
|         if self.max_length is not None:
 | |
|             message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
 | |
|             self.validators.append(MaxLengthValidator(self.max_length, message=message))
 | |
|         if self.min_length is not None:
 | |
|             message = lazy_format(self.error_messages['min_length'], min_length=self.min_length)
 | |
|             self.validators.append(MinLengthValidator(self.min_length, message=message))
 | |
| 
 | |
|     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, default=empty)
 | |
| 
 | |
|         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, default=[])
 | |
|         if isinstance(data, (str, 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.run_child_validation(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]
 | |
| 
 | |
|     def run_child_validation(self, data):
 | |
|         result = []
 | |
|         errors = {}
 | |
| 
 | |
|         for idx, item in enumerate(data):
 | |
|             try:
 | |
|                 result.append(self.child.run_validation(item))
 | |
|             except ValidationError as e:
 | |
|                 errors[idx] = e.detail
 | |
|             except DjangoValidationError as e:
 | |
|                 errors[idx] = get_error_detail(e)
 | |
| 
 | |
|         if not errors:
 | |
|             return result
 | |
|         raise ValidationError(errors)
 | |
| 
 | |
| 
 | |
| class DictField(Field):
 | |
|     child = _UnvalidatedField()
 | |
|     initial = {}
 | |
|     default_error_messages = {
 | |
|         'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".'),
 | |
|         'empty': _('This dictionary may not be empty.'),
 | |
|     }
 | |
| 
 | |
|     def __init__(self, **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().__init__(**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__)
 | |
|         if not self.allow_empty and len(data) == 0:
 | |
|             self.fail('empty')
 | |
| 
 | |
|         return self.run_child_validation(data)
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         return {
 | |
|             str(key): self.child.to_representation(val) if val is not None else None
 | |
|             for key, val in value.items()
 | |
|         }
 | |
| 
 | |
|     def run_child_validation(self, data):
 | |
|         result = {}
 | |
|         errors = {}
 | |
| 
 | |
|         for key, value in data.items():
 | |
|             key = str(key)
 | |
| 
 | |
|             try:
 | |
|                 result[key] = self.child.run_validation(value)
 | |
|             except ValidationError as e:
 | |
|                 errors[key] = e.detail
 | |
| 
 | |
|         if not errors:
 | |
|             return result
 | |
|         raise ValidationError(errors)
 | |
| 
 | |
| 
 | |
| class HStoreField(DictField):
 | |
|     child = CharField(allow_blank=True, allow_null=True)
 | |
| 
 | |
|     def __init__(self, **kwargs):
 | |
|         super().__init__(**kwargs)
 | |
|         assert isinstance(self.child, CharField), (
 | |
|             "The `child` argument must be an instance of `CharField`, "
 | |
|             "as the hstore extension stores values as strings."
 | |
|         )
 | |
| 
 | |
| 
 | |
| class JSONField(Field):
 | |
|     default_error_messages = {
 | |
|         'invalid': _('Value must be valid JSON.')
 | |
|     }
 | |
| 
 | |
|     # Workaround for isinstance calls when importing the field isn't possible
 | |
|     _is_jsonfield = True
 | |
| 
 | |
|     def __init__(self, **kwargs):
 | |
|         self.binary = kwargs.pop('binary', False)
 | |
|         self.encoder = kwargs.pop('encoder', None)
 | |
|         self.decoder = kwargs.pop('decoder', None)
 | |
|         super().__init__(**kwargs)
 | |
| 
 | |
|     def get_value(self, dictionary):
 | |
|         if html.is_html_input(dictionary) and self.field_name in dictionary:
 | |
|             # When HTML form input is used, mark up the input
 | |
|             # as being a JSON string, rather than a JSON primitive.
 | |
|             class JSONString(str):
 | |
|                 def __new__(cls, value):
 | |
|                     ret = str.__new__(cls, value)
 | |
|                     ret.is_json_string = True
 | |
|                     return ret
 | |
|             return JSONString(dictionary[self.field_name])
 | |
|         return dictionary.get(self.field_name, empty)
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         try:
 | |
|             if self.binary or getattr(data, 'is_json_string', False):
 | |
|                 if isinstance(data, bytes):
 | |
|                     data = data.decode()
 | |
|                 return json.loads(data, cls=self.decoder)
 | |
|             else:
 | |
|                 json.dumps(data, cls=self.encoder)
 | |
|         except (TypeError, ValueError):
 | |
|             self.fail('invalid')
 | |
|         return data
 | |
| 
 | |
|     def to_representation(self, value):
 | |
|         if self.binary:
 | |
|             value = json.dumps(value, cls=self.encoder)
 | |
|             value = value.encode()
 | |
|         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 its 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().__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().__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(Serializer):
 | |
|         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().__init__(**kwargs)
 | |
| 
 | |
|     def bind(self, field_name, parent):
 | |
|         # The method name defaults to `get_{field_name}`.
 | |
|         if self.method_name is None:
 | |
|             self.method_name = f'get_{field_name}'
 | |
| 
 | |
|         super().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.
 | |
|         self.max_length = kwargs.pop('max_length', None)
 | |
|         super().__init__(**kwargs)
 | |
|         if self.max_length is not None:
 | |
|             message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
 | |
|             self.validators.append(
 | |
|                 MaxLengthValidator(self.max_length, message=message))
 | |
| 
 | |
|     def to_internal_value(self, data):
 | |
|         rel = self.model_field.remote_field
 | |
|         if rel is not None:
 | |
|             return rel.model._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 = self.model_field.value_from_object(obj)
 | |
|         if is_protected_type(value):
 | |
|             return value
 | |
|         return self.model_field.value_to_string(obj)
 |