2014-09-18 14:20:56 +04:00
|
|
|
"""
|
|
|
|
Helper functions for mapping model fields to a dictionary of default
|
2016-08-08 11:32:22 +03:00
|
|
|
keyword arguments that should be used for their equivalent serializer fields.
|
2014-09-18 14:20:56 +04:00
|
|
|
"""
|
2015-06-18 16:38:29 +03:00
|
|
|
import inspect
|
|
|
|
|
|
|
|
from django.core import validators
|
2015-06-25 23:55:51 +03:00
|
|
|
from django.db import models
|
2014-09-18 14:20:56 +04:00
|
|
|
from django.utils.text import capfirst
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2017-10-05 21:41:38 +03:00
|
|
|
from rest_framework.compat import postgres_fields
|
2014-09-29 12:24:03 +04:00
|
|
|
from rest_framework.validators import UniqueValidator
|
2014-09-18 14:20:56 +04:00
|
|
|
|
2015-01-05 13:52:18 +03:00
|
|
|
NUMERIC_FIELD_TYPES = (
|
2018-04-24 10:24:05 +03:00
|
|
|
models.IntegerField, models.FloatField, models.DecimalField, models.DurationField,
|
2015-01-05 13:52:18 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2019-04-30 18:53:44 +03:00
|
|
|
class ClassLookupDict:
|
2014-09-18 14:20:56 +04:00
|
|
|
"""
|
2014-09-24 17:09:49 +04:00
|
|
|
Takes a dictionary with classes as keys.
|
|
|
|
Lookups against this object will traverses the object's inheritance
|
|
|
|
hierarchy in method resolution order, and returns the first matching value
|
2014-09-18 14:20:56 +04:00
|
|
|
from the dictionary or raises a KeyError if nothing matches.
|
|
|
|
"""
|
2014-09-24 17:09:49 +04:00
|
|
|
def __init__(self, mapping):
|
|
|
|
self.mapping = mapping
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
2014-10-02 19:24:24 +04:00
|
|
|
if hasattr(key, '_proxy_class'):
|
|
|
|
# Deal with proxy classes. Ie. BoundField behaves as if it
|
|
|
|
# is a Field instance when using ClassLookupDict.
|
|
|
|
base_class = key._proxy_class
|
|
|
|
else:
|
|
|
|
base_class = key.__class__
|
|
|
|
|
|
|
|
for cls in inspect.getmro(base_class):
|
2014-09-24 17:09:49 +04:00
|
|
|
if cls in self.mapping:
|
|
|
|
return self.mapping[cls]
|
2015-03-26 21:13:35 +03:00
|
|
|
raise KeyError('Class %s not found in lookup.' % base_class.__name__)
|
2014-09-18 14:20:56 +04:00
|
|
|
|
2015-01-23 18:32:21 +03:00
|
|
|
def __setitem__(self, key, value):
|
|
|
|
self.mapping[key] = value
|
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
|
|
|
|
def needs_label(model_field, field_name):
|
|
|
|
"""
|
|
|
|
Returns `True` if the label based on the model's verbose name
|
|
|
|
is not equal to the default label it would have based on it's field name.
|
|
|
|
"""
|
|
|
|
default_label = field_name.replace('_', ' ').capitalize()
|
|
|
|
return capfirst(model_field.verbose_name) != default_label
|
|
|
|
|
|
|
|
|
|
|
|
def get_detail_view_name(model):
|
|
|
|
"""
|
|
|
|
Given a model class, return the view name to use for URL relationships
|
|
|
|
that refer to instances of the model.
|
|
|
|
"""
|
|
|
|
return '%(model_name)s-detail' % {
|
|
|
|
'app_label': model._meta.app_label,
|
|
|
|
'model_name': model._meta.object_name.lower()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def get_field_kwargs(field_name, model_field):
|
|
|
|
"""
|
|
|
|
Creates a default instance of a basic non-relational field.
|
|
|
|
"""
|
|
|
|
kwargs = {}
|
2014-12-09 19:25:10 +03:00
|
|
|
validator_kwarg = list(model_field.validators)
|
2014-09-18 14:20:56 +04:00
|
|
|
|
2014-09-23 17:15:00 +04:00
|
|
|
# The following will only be used by ModelField classes.
|
|
|
|
# Gets removed for everything else.
|
|
|
|
kwargs['model_field'] = model_field
|
2014-09-18 14:20:56 +04:00
|
|
|
|
|
|
|
if model_field.verbose_name and needs_label(model_field, field_name):
|
|
|
|
kwargs['label'] = capfirst(model_field.verbose_name)
|
|
|
|
|
|
|
|
if model_field.help_text:
|
|
|
|
kwargs['help_text'] = model_field.help_text
|
|
|
|
|
2014-10-08 19:09:37 +04:00
|
|
|
max_digits = getattr(model_field, 'max_digits', None)
|
|
|
|
if max_digits is not None:
|
|
|
|
kwargs['max_digits'] = max_digits
|
|
|
|
|
|
|
|
decimal_places = getattr(model_field, 'decimal_places', None)
|
|
|
|
if decimal_places is not None:
|
|
|
|
kwargs['decimal_places'] = decimal_places
|
|
|
|
|
2018-12-19 17:37:52 +03:00
|
|
|
if isinstance(model_field, models.SlugField):
|
|
|
|
kwargs['allow_unicode'] = model_field.allow_unicode
|
|
|
|
|
2019-12-04 23:24:49 +03:00
|
|
|
if isinstance(model_field, models.TextField) and not model_field.choices or \
|
|
|
|
(postgres_fields and isinstance(model_field, postgres_fields.JSONField)):
|
2014-12-22 20:05:07 +03:00
|
|
|
kwargs['style'] = {'base_template': 'textarea.html'}
|
2014-10-08 19:09:37 +04:00
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
if isinstance(model_field, models.AutoField) or not model_field.editable:
|
2014-09-23 17:15:00 +04:00
|
|
|
# If this field is read-only, then return early.
|
|
|
|
# Further keyword arguments are not valid.
|
2014-09-18 14:20:56 +04:00
|
|
|
kwargs['read_only'] = True
|
2014-09-23 17:15:00 +04:00
|
|
|
return kwargs
|
2014-09-18 14:20:56 +04:00
|
|
|
|
2014-11-13 22:28:57 +03:00
|
|
|
if model_field.has_default() or model_field.blank or model_field.null:
|
2014-09-23 17:15:00 +04:00
|
|
|
kwargs['required'] = False
|
2014-09-18 14:20:56 +04:00
|
|
|
|
2014-09-23 17:30:17 +04:00
|
|
|
if model_field.null and not isinstance(model_field, models.NullBooleanField):
|
2014-09-23 17:15:00 +04:00
|
|
|
kwargs['allow_null'] = True
|
|
|
|
|
2019-03-22 15:29:45 +03:00
|
|
|
if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))):
|
2014-09-23 17:15:00 +04:00
|
|
|
kwargs['allow_blank'] = True
|
|
|
|
|
2019-07-02 03:34:34 +03:00
|
|
|
if not model_field.blank and (postgres_fields and isinstance(model_field, postgres_fields.ArrayField)):
|
|
|
|
kwargs['allow_empty'] = False
|
|
|
|
|
2015-10-23 16:58:06 +03:00
|
|
|
if isinstance(model_field, models.FilePathField):
|
|
|
|
kwargs['path'] = model_field.path
|
|
|
|
|
|
|
|
if model_field.match is not None:
|
|
|
|
kwargs['match'] = model_field.match
|
|
|
|
|
|
|
|
if model_field.recursive is not False:
|
|
|
|
kwargs['recursive'] = model_field.recursive
|
|
|
|
|
|
|
|
if model_field.allow_files is not True:
|
|
|
|
kwargs['allow_files'] = model_field.allow_files
|
|
|
|
|
|
|
|
if model_field.allow_folders is not False:
|
|
|
|
kwargs['allow_folders'] = model_field.allow_folders
|
|
|
|
|
2015-08-06 13:43:03 +03:00
|
|
|
if model_field.choices:
|
|
|
|
kwargs['choices'] = model_field.choices
|
2017-03-27 22:08:21 +03:00
|
|
|
else:
|
|
|
|
# Ensure that max_value is passed explicitly as a keyword arg,
|
|
|
|
# rather than as a validator.
|
|
|
|
max_value = next((
|
|
|
|
validator.limit_value for validator in validator_kwarg
|
|
|
|
if isinstance(validator, validators.MaxValueValidator)
|
|
|
|
), None)
|
|
|
|
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
|
|
|
|
kwargs['max_value'] = max_value
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if not isinstance(validator, validators.MaxValueValidator)
|
|
|
|
]
|
|
|
|
|
2017-05-04 07:13:58 +03:00
|
|
|
# Ensure that min_value is passed explicitly as a keyword arg,
|
2017-03-27 22:08:21 +03:00
|
|
|
# rather than as a validator.
|
|
|
|
min_value = next((
|
|
|
|
validator.limit_value for validator in validator_kwarg
|
|
|
|
if isinstance(validator, validators.MinValueValidator)
|
|
|
|
), None)
|
|
|
|
if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
|
|
|
|
kwargs['min_value'] = min_value
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if not isinstance(validator, validators.MinValueValidator)
|
|
|
|
]
|
|
|
|
|
|
|
|
# URLField does not need to include the URLValidator argument,
|
|
|
|
# as it is explicitly added in.
|
|
|
|
if isinstance(model_field, models.URLField):
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if not isinstance(validator, validators.URLValidator)
|
|
|
|
]
|
|
|
|
|
|
|
|
# EmailField does not need to include the validate_email argument,
|
|
|
|
# as it is explicitly added in.
|
|
|
|
if isinstance(model_field, models.EmailField):
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if validator is not validators.validate_email
|
|
|
|
]
|
|
|
|
|
|
|
|
# SlugField do not need to include the 'validate_slug' argument,
|
|
|
|
if isinstance(model_field, models.SlugField):
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if validator is not validators.validate_slug
|
|
|
|
]
|
|
|
|
|
|
|
|
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
|
|
|
|
if isinstance(model_field, models.GenericIPAddressField):
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if validator is not validators.validate_ipv46_address
|
|
|
|
]
|
|
|
|
# Our decimal validation is handled in the field code, not validator code.
|
2017-10-05 21:41:38 +03:00
|
|
|
if isinstance(model_field, models.DecimalField):
|
2017-03-27 22:08:21 +03:00
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
2017-10-05 21:41:38 +03:00
|
|
|
if not isinstance(validator, validators.DecimalValidator)
|
2017-03-27 22:08:21 +03:00
|
|
|
]
|
2015-09-28 13:44:07 +03:00
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
# Ensure that max_length is passed explicitly as a keyword arg,
|
|
|
|
# rather than as a validator.
|
|
|
|
max_length = getattr(model_field, 'max_length', None)
|
2019-03-22 15:29:45 +03:00
|
|
|
if max_length is not None and (isinstance(model_field, (models.CharField, models.TextField, models.FileField))):
|
2014-09-18 14:20:56 +04:00
|
|
|
kwargs['max_length'] = max_length
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if not isinstance(validator, validators.MaxLengthValidator)
|
|
|
|
]
|
|
|
|
|
|
|
|
# Ensure that min_length is passed explicitly as a keyword arg,
|
|
|
|
# rather than as a validator.
|
2014-09-23 17:15:00 +04:00
|
|
|
min_length = next((
|
|
|
|
validator.limit_value for validator in validator_kwarg
|
|
|
|
if isinstance(validator, validators.MinLengthValidator)
|
|
|
|
), None)
|
2015-01-05 13:52:18 +03:00
|
|
|
if min_length is not None and isinstance(model_field, models.CharField):
|
2014-09-18 14:20:56 +04:00
|
|
|
kwargs['min_length'] = min_length
|
|
|
|
validator_kwarg = [
|
|
|
|
validator for validator in validator_kwarg
|
|
|
|
if not isinstance(validator, validators.MinLengthValidator)
|
|
|
|
]
|
|
|
|
|
2014-09-29 12:24:03 +04:00
|
|
|
if getattr(model_field, 'unique', False):
|
2015-09-23 16:07:56 +03:00
|
|
|
unique_error_message = model_field.error_messages.get('unique', None)
|
|
|
|
if unique_error_message:
|
|
|
|
unique_error_message = unique_error_message % {
|
2016-07-26 17:12:51 +03:00
|
|
|
'model_name': model_field.model._meta.verbose_name,
|
2015-09-23 16:07:56 +03:00
|
|
|
'field_label': model_field.verbose_name
|
|
|
|
}
|
|
|
|
validator = UniqueValidator(
|
|
|
|
queryset=model_field.model._default_manager,
|
|
|
|
message=unique_error_message)
|
2014-09-29 12:24:03 +04:00
|
|
|
validator_kwarg.append(validator)
|
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
if validator_kwarg:
|
|
|
|
kwargs['validators'] = validator_kwarg
|
|
|
|
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
|
|
|
|
def get_relation_kwargs(field_name, relation_info):
|
|
|
|
"""
|
|
|
|
Creates a default instance of a flat relational field.
|
|
|
|
"""
|
2016-10-13 14:43:43 +03:00
|
|
|
model_field, related_model, to_many, to_field, has_through_model, reverse = relation_info
|
2014-09-18 14:20:56 +04:00
|
|
|
kwargs = {
|
|
|
|
'queryset': related_model._default_manager,
|
|
|
|
'view_name': get_detail_view_name(related_model)
|
|
|
|
}
|
|
|
|
|
|
|
|
if to_many:
|
|
|
|
kwargs['many'] = True
|
|
|
|
|
2015-10-21 15:32:16 +03:00
|
|
|
if to_field:
|
|
|
|
kwargs['to_field'] = to_field
|
|
|
|
|
2019-01-08 16:49:47 +03:00
|
|
|
limit_choices_to = model_field and model_field.get_limit_choices_to()
|
|
|
|
if limit_choices_to:
|
2019-02-25 13:49:29 +03:00
|
|
|
if not isinstance(limit_choices_to, models.Q):
|
|
|
|
limit_choices_to = models.Q(**limit_choices_to)
|
|
|
|
kwargs['queryset'] = kwargs['queryset'].filter(limit_choices_to)
|
2019-01-08 16:49:47 +03:00
|
|
|
|
2014-09-18 14:20:56 +04:00
|
|
|
if has_through_model:
|
|
|
|
kwargs['read_only'] = True
|
|
|
|
kwargs.pop('queryset', None)
|
|
|
|
|
|
|
|
if model_field:
|
|
|
|
if model_field.verbose_name and needs_label(model_field, field_name):
|
|
|
|
kwargs['label'] = capfirst(model_field.verbose_name)
|
2015-09-21 21:16:52 +03:00
|
|
|
help_text = model_field.help_text
|
2014-09-18 14:20:56 +04:00
|
|
|
if help_text:
|
|
|
|
kwargs['help_text'] = help_text
|
2014-09-23 17:15:00 +04:00
|
|
|
if not model_field.editable:
|
|
|
|
kwargs['read_only'] = True
|
|
|
|
kwargs.pop('queryset', None)
|
|
|
|
if kwargs.get('read_only', False):
|
|
|
|
# If this field is read-only, then return early.
|
|
|
|
# No further keyword arguments are valid.
|
|
|
|
return kwargs
|
2015-07-30 19:03:08 +03:00
|
|
|
|
2015-09-03 12:12:52 +03:00
|
|
|
if model_field.has_default() or model_field.blank or model_field.null:
|
2014-09-23 17:15:00 +04:00
|
|
|
kwargs['required'] = False
|
2015-09-03 12:12:52 +03:00
|
|
|
if model_field.null:
|
2014-09-23 17:15:00 +04:00
|
|
|
kwargs['allow_null'] = True
|
2014-11-07 13:51:08 +03:00
|
|
|
if model_field.validators:
|
|
|
|
kwargs['validators'] = model_field.validators
|
2014-10-07 20:04:53 +04:00
|
|
|
if getattr(model_field, 'unique', False):
|
|
|
|
validator = UniqueValidator(queryset=model_field.model._default_manager)
|
2014-11-07 13:51:08 +03:00
|
|
|
kwargs['validators'] = kwargs.get('validators', []) + [validator]
|
2015-07-30 19:03:08 +03:00
|
|
|
if to_many and not model_field.blank:
|
|
|
|
kwargs['allow_empty'] = False
|
2014-09-18 14:20:56 +04:00
|
|
|
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
|
|
|
|
def get_nested_relation_kwargs(relation_info):
|
|
|
|
kwargs = {'read_only': True}
|
|
|
|
if relation_info.to_many:
|
|
|
|
kwargs['many'] = True
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
|
|
|
|
def get_url_kwargs(model_field):
|
|
|
|
return {
|
|
|
|
'view_name': get_detail_view_name(model_field)
|
|
|
|
}
|