mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
build_*_field methods
This commit is contained in:
parent
f72928ea98
commit
75e81b8254
|
@ -696,7 +696,7 @@ class ModelSerializer(Serializer):
|
||||||
you need you should either declare the extra/differing fields explicitly on
|
you need you should either declare the extra/differing fields explicitly on
|
||||||
the serializer class, or simply use a `Serializer` class.
|
the serializer class, or simply use a `Serializer` class.
|
||||||
"""
|
"""
|
||||||
_field_mapping = ClassLookupDict({
|
serializer_field_mapping = {
|
||||||
models.AutoField: IntegerField,
|
models.AutoField: IntegerField,
|
||||||
models.BigIntegerField: IntegerField,
|
models.BigIntegerField: IntegerField,
|
||||||
models.BooleanField: BooleanField,
|
models.BooleanField: BooleanField,
|
||||||
|
@ -719,8 +719,8 @@ class ModelSerializer(Serializer):
|
||||||
models.TextField: CharField,
|
models.TextField: CharField,
|
||||||
models.TimeField: TimeField,
|
models.TimeField: TimeField,
|
||||||
models.URLField: URLField,
|
models.URLField: URLField,
|
||||||
})
|
}
|
||||||
_related_class = PrimaryKeyRelatedField
|
serializer_related_class = PrimaryKeyRelatedField
|
||||||
|
|
||||||
# Default `create` and `update` behavior...
|
# Default `create` and `update` behavior...
|
||||||
|
|
||||||
|
@ -793,6 +793,417 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
# Determine the fields to apply...
|
||||||
|
|
||||||
|
def get_fields(self):
|
||||||
|
"""
|
||||||
|
Return the dict of field names -> field instances that should be
|
||||||
|
used for `self.fields` when instantiating the serializer.
|
||||||
|
"""
|
||||||
|
declared_fields = copy.deepcopy(self._declared_fields)
|
||||||
|
model = getattr(self.Meta, 'model')
|
||||||
|
depth = getattr(self.Meta, 'depth', 0)
|
||||||
|
|
||||||
|
# Retrieve metadata about fields & relationships on the model class.
|
||||||
|
info = model_meta.get_field_info(model)
|
||||||
|
field_names = self.get_field_names(declared_fields, info)
|
||||||
|
|
||||||
|
# Determine any extra field arguments and hidden fields that
|
||||||
|
# should be included
|
||||||
|
extra_kwargs = self.get_extra_kwargs()
|
||||||
|
extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs(
|
||||||
|
field_names, declared_fields, extra_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now determine the fields that should be included on the serializer.
|
||||||
|
ret = OrderedDict()
|
||||||
|
for field_name in field_names:
|
||||||
|
if field_name in declared_fields:
|
||||||
|
# Field is explicitly declared on the class, use that.
|
||||||
|
ret[field_name] = declared_fields[field_name]
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Determine the serializer field class and keyword arguments.
|
||||||
|
field_cls, kwargs = self.build_field(field_name, info, model, depth)
|
||||||
|
|
||||||
|
# Populate any kwargs defined in `Meta.extra_kwargs`
|
||||||
|
kwargs = self.build_field_kwargs(kwargs, extra_kwargs, field_name)
|
||||||
|
|
||||||
|
# Create the serializer field.
|
||||||
|
ret[field_name] = field_cls(**kwargs)
|
||||||
|
|
||||||
|
# Add in any hidden fields.
|
||||||
|
ret.update(hidden_fields)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
# Methods for determining the set of field names to include...
|
||||||
|
|
||||||
|
def get_field_names(self, declared_fields, info):
|
||||||
|
"""
|
||||||
|
Returns the list of all field names that should be created when
|
||||||
|
instantiating this serializer class. This is based on the default
|
||||||
|
set of fields, but also takes into account the `Meta.fields` or
|
||||||
|
`Meta.exclude` options if they have been specified.
|
||||||
|
"""
|
||||||
|
fields = getattr(self.Meta, 'fields', None)
|
||||||
|
exclude = getattr(self.Meta, 'exclude', None)
|
||||||
|
|
||||||
|
if fields and not isinstance(fields, (list, tuple)):
|
||||||
|
raise TypeError(
|
||||||
|
'The `fields` option must be a list or tuple. Got %s.' %
|
||||||
|
type(fields).__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
if exclude and not isinstance(exclude, (list, tuple)):
|
||||||
|
raise TypeError(
|
||||||
|
'The `exclude` option must be a list or tuple. Got %s.' %
|
||||||
|
type(exclude).__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not (fields and exclude), (
|
||||||
|
"Cannot set both 'fields' and 'exclude' options on "
|
||||||
|
"serializer {serializer_class}.".format(
|
||||||
|
serializer_class=self.__class__.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if fields is not None:
|
||||||
|
# Ensure that all declared fields have also been included in the
|
||||||
|
# `Meta.fields` option.
|
||||||
|
for field_name in declared_fields:
|
||||||
|
assert field_name in fields, (
|
||||||
|
"The field '{field_name}' was declared on serializer "
|
||||||
|
"{serializer_class}, but has not been included in the "
|
||||||
|
"'fields' option.".format(
|
||||||
|
field_name=field_name,
|
||||||
|
serializer_class=self.__class__.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return fields
|
||||||
|
|
||||||
|
# Use the default set of field names if `Meta.fields` is not specified.
|
||||||
|
fields = self.get_default_field_names(declared_fields, info)
|
||||||
|
|
||||||
|
if exclude is not None:
|
||||||
|
# If `Meta.exclude` is included, then remove those fields.
|
||||||
|
for field_name in exclude:
|
||||||
|
assert field_name in fields, (
|
||||||
|
"The field '{field_name}' was include on serializer "
|
||||||
|
"{serializer_class} in the 'exclude' option, but does "
|
||||||
|
"not match any model field.".format(
|
||||||
|
field_name=field_name,
|
||||||
|
serializer_class=self.__class__.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fields.remove(field_name)
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_default_field_names(self, declared_fields, model_info):
|
||||||
|
"""
|
||||||
|
Return the default list of field names that will be used if the
|
||||||
|
`Meta.fields` option is not specified.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
[model_info.pk.name] +
|
||||||
|
list(declared_fields.keys()) +
|
||||||
|
list(model_info.fields.keys()) +
|
||||||
|
list(model_info.forward_relations.keys())
|
||||||
|
)
|
||||||
|
|
||||||
|
# Methods for constructing serializer fields...
|
||||||
|
|
||||||
|
def build_field(self, field_name, info, model, nested_depth):
|
||||||
|
"""
|
||||||
|
Return a two tuple of (cls, kwargs) to build a serializer field with.
|
||||||
|
"""
|
||||||
|
if field_name in info.fields_and_pk:
|
||||||
|
return self.build_standard_field(field_name, info, model)
|
||||||
|
|
||||||
|
elif field_name in info.relations:
|
||||||
|
if not nested_depth:
|
||||||
|
return self.build_relational_field(field_name, info, model)
|
||||||
|
else:
|
||||||
|
return self.build_nested_field(field_name, info, model, nested_depth)
|
||||||
|
|
||||||
|
elif hasattr(model, field_name):
|
||||||
|
return self.build_property_field(field_name, info, model)
|
||||||
|
|
||||||
|
elif field_name == api_settings.URL_FIELD_NAME:
|
||||||
|
return self.build_url_field(field_name, info, model)
|
||||||
|
|
||||||
|
return self.build_unknown_field(field_name, info, model)
|
||||||
|
|
||||||
|
def build_standard_field(self, field_name, info, model):
|
||||||
|
"""
|
||||||
|
Create regular model fields.
|
||||||
|
"""
|
||||||
|
field_mapping = ClassLookupDict(self.serializer_field_mapping)
|
||||||
|
model_field = info.fields_and_pk[field_name]
|
||||||
|
|
||||||
|
field_cls = field_mapping[model_field]
|
||||||
|
kwargs = get_field_kwargs(field_name, model_field)
|
||||||
|
|
||||||
|
if 'choices' in kwargs:
|
||||||
|
# Fields with choices get coerced into `ChoiceField`
|
||||||
|
# instead of using their regular typed field.
|
||||||
|
field_cls = ChoiceField
|
||||||
|
if not issubclass(field_cls, ModelField):
|
||||||
|
# `model_field` is only valid for the fallback case of
|
||||||
|
# `ModelField`, which is used when no other typed field
|
||||||
|
# matched to the model field.
|
||||||
|
kwargs.pop('model_field', None)
|
||||||
|
if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField):
|
||||||
|
# `allow_blank` is only valid for textual fields.
|
||||||
|
kwargs.pop('allow_blank', None)
|
||||||
|
|
||||||
|
return field_cls, kwargs
|
||||||
|
|
||||||
|
def build_relational_field(self, field_name, info, model):
|
||||||
|
"""
|
||||||
|
Create fields for forward and reverse relationships.
|
||||||
|
"""
|
||||||
|
relation_info = info.relations[field_name]
|
||||||
|
|
||||||
|
field_cls = self.serializer_related_class
|
||||||
|
kwargs = get_relation_kwargs(field_name, relation_info)
|
||||||
|
|
||||||
|
# `view_name` is only valid for hyperlinked relationships.
|
||||||
|
if not issubclass(field_cls, HyperlinkedRelatedField):
|
||||||
|
kwargs.pop('view_name', None)
|
||||||
|
|
||||||
|
return field_cls, kwargs
|
||||||
|
|
||||||
|
def build_nested_field(self, field_name, info, model, nested_depth):
|
||||||
|
"""
|
||||||
|
Create nested fields for forward and reverse relationships.
|
||||||
|
"""
|
||||||
|
relation_info = info.relations[field_name]
|
||||||
|
|
||||||
|
class NestedSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = relation_info.related
|
||||||
|
depth = nested_depth - 1
|
||||||
|
|
||||||
|
field_cls = NestedSerializer
|
||||||
|
kwargs = get_nested_relation_kwargs(relation_info)
|
||||||
|
|
||||||
|
return field_cls, kwargs
|
||||||
|
|
||||||
|
def build_property_field(self, field_name, info, model):
|
||||||
|
"""
|
||||||
|
Create a read only field for model methods and properties.
|
||||||
|
"""
|
||||||
|
field_cls = ReadOnlyField
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
return field_cls, kwargs
|
||||||
|
|
||||||
|
def build_url_field(self, field_name, info, model):
|
||||||
|
"""
|
||||||
|
Create a field representing the object's own URL.
|
||||||
|
"""
|
||||||
|
field_cls = HyperlinkedIdentityField
|
||||||
|
kwargs = get_url_kwargs(model)
|
||||||
|
|
||||||
|
return field_cls, kwargs
|
||||||
|
|
||||||
|
def build_unknown_field(self, field_name, info, model):
|
||||||
|
"""
|
||||||
|
Raise an error on any unknown fields.
|
||||||
|
"""
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'Field name `%s` is not valid for model `%s`.' %
|
||||||
|
(field_name, model.__class__.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_field_kwargs(self, kwargs, extra_kwargs, field_name):
|
||||||
|
"""
|
||||||
|
Include an 'extra_kwargs' that have been included for this field,
|
||||||
|
possibly removing any incompatible existing keyword arguments.
|
||||||
|
"""
|
||||||
|
extras = extra_kwargs.get(field_name, {})
|
||||||
|
|
||||||
|
if extras.get('read_only', False):
|
||||||
|
for attr in [
|
||||||
|
'required', 'default', 'allow_blank', 'allow_null',
|
||||||
|
'min_length', 'max_length', 'min_value', 'max_value',
|
||||||
|
'validators', 'queryset'
|
||||||
|
]:
|
||||||
|
kwargs.pop(attr, None)
|
||||||
|
|
||||||
|
if extras.get('default') and kwargs.get('required') is False:
|
||||||
|
kwargs.pop('required')
|
||||||
|
|
||||||
|
kwargs.update(extras)
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
# Methods for determining additional keyword arguments to apply...
|
||||||
|
|
||||||
|
def get_extra_kwargs(self):
|
||||||
|
"""
|
||||||
|
Return a dictionary mapping field names to a dictionary of
|
||||||
|
additional keyword arguments.
|
||||||
|
"""
|
||||||
|
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
|
||||||
|
|
||||||
|
read_only_fields = getattr(self.Meta, 'read_only_fields', None)
|
||||||
|
if read_only_fields is not None:
|
||||||
|
for field_name in read_only_fields:
|
||||||
|
kwargs = extra_kwargs.get(field_name, {})
|
||||||
|
kwargs['read_only'] = True
|
||||||
|
extra_kwargs[field_name] = kwargs
|
||||||
|
|
||||||
|
# These are all pending deprecation.
|
||||||
|
write_only_fields = getattr(self.Meta, 'write_only_fields', None)
|
||||||
|
if write_only_fields is not None:
|
||||||
|
warnings.warn(
|
||||||
|
"The `Meta.write_only_fields` option is pending deprecation. "
|
||||||
|
"Use `Meta.extra_kwargs={<field_name>: {'write_only': True}}` instead.",
|
||||||
|
PendingDeprecationWarning,
|
||||||
|
stacklevel=3
|
||||||
|
)
|
||||||
|
for field_name in write_only_fields:
|
||||||
|
kwargs = extra_kwargs.get(field_name, {})
|
||||||
|
kwargs['write_only'] = True
|
||||||
|
extra_kwargs[field_name] = kwargs
|
||||||
|
|
||||||
|
view_name = getattr(self.Meta, 'view_name', None)
|
||||||
|
if view_name is not None:
|
||||||
|
warnings.warn(
|
||||||
|
"The `Meta.view_name` option is pending deprecation. "
|
||||||
|
"Use `Meta.extra_kwargs={'url': {'view_name': ...}}` instead.",
|
||||||
|
PendingDeprecationWarning,
|
||||||
|
stacklevel=3
|
||||||
|
)
|
||||||
|
kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {})
|
||||||
|
kwargs['view_name'] = view_name
|
||||||
|
extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs
|
||||||
|
|
||||||
|
lookup_field = getattr(self.Meta, 'lookup_field', None)
|
||||||
|
if lookup_field is not None:
|
||||||
|
warnings.warn(
|
||||||
|
"The `Meta.lookup_field` option is pending deprecation. "
|
||||||
|
"Use `Meta.extra_kwargs={'url': {'lookup_field': ...}}` instead.",
|
||||||
|
PendingDeprecationWarning,
|
||||||
|
stacklevel=3
|
||||||
|
)
|
||||||
|
kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {})
|
||||||
|
kwargs['lookup_field'] = lookup_field
|
||||||
|
extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs
|
||||||
|
|
||||||
|
return extra_kwargs
|
||||||
|
|
||||||
|
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
|
||||||
|
"""
|
||||||
|
Return any additional field options that need to be included as a
|
||||||
|
result of uniqueness constraints on the model. This is returned as
|
||||||
|
a two-tuple of:
|
||||||
|
|
||||||
|
('dict of updated extra kwargs', 'mapping of hidden fields')
|
||||||
|
"""
|
||||||
|
model = getattr(self.Meta, 'model')
|
||||||
|
model_fields = self._get_model_fields(
|
||||||
|
field_names, declared_fields, extra_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Determine if we need any additional `HiddenField` or extra keyword
|
||||||
|
# arguments to deal with `unique_for` dates that are required to
|
||||||
|
# be in the input data in order to validate it.
|
||||||
|
unique_constraint_names = set()
|
||||||
|
|
||||||
|
for model_field in model_fields.values():
|
||||||
|
# Include each of the `unique_for_*` field names.
|
||||||
|
unique_constraint_names |= set([
|
||||||
|
model_field.unique_for_date,
|
||||||
|
model_field.unique_for_month,
|
||||||
|
model_field.unique_for_year
|
||||||
|
])
|
||||||
|
|
||||||
|
unique_constraint_names -= set([None])
|
||||||
|
|
||||||
|
# Include each of the `unique_together` field names,
|
||||||
|
# so long as all the field names are included on the serializer.
|
||||||
|
for parent_class in [model] + list(model._meta.parents.keys()):
|
||||||
|
for unique_together_list in parent_class._meta.unique_together:
|
||||||
|
if set(field_names).issuperset(set(unique_together_list)):
|
||||||
|
unique_constraint_names |= set(unique_together_list)
|
||||||
|
|
||||||
|
# Now we have all the field names that have uniqueness constraints
|
||||||
|
# applied, we can add the extra 'required=...' or 'default=...'
|
||||||
|
# arguments that are appropriate to these fields, or add a `HiddenField` for it.
|
||||||
|
hidden_fields = {}
|
||||||
|
uniqueness_extra_kwargs = {}
|
||||||
|
|
||||||
|
for unique_constraint_name in unique_constraint_names:
|
||||||
|
# Get the model field that is referred too.
|
||||||
|
unique_constraint_field = model._meta.get_field(unique_constraint_name)
|
||||||
|
|
||||||
|
if getattr(unique_constraint_field, 'auto_now_add', None):
|
||||||
|
default = CreateOnlyDefault(timezone.now)
|
||||||
|
elif getattr(unique_constraint_field, 'auto_now', None):
|
||||||
|
default = timezone.now
|
||||||
|
elif unique_constraint_field.has_default():
|
||||||
|
default = unique_constraint_field.default
|
||||||
|
else:
|
||||||
|
default = empty
|
||||||
|
|
||||||
|
if unique_constraint_name in model_fields:
|
||||||
|
# The corresponding field is present in the serializer
|
||||||
|
if default is empty:
|
||||||
|
uniqueness_extra_kwargs[unique_constraint_name] = {'required': True}
|
||||||
|
else:
|
||||||
|
uniqueness_extra_kwargs[unique_constraint_name] = {'default': default}
|
||||||
|
elif default is not empty:
|
||||||
|
# The corresponding field is not present in the,
|
||||||
|
# serializer. We have a default to use for it, so
|
||||||
|
# add in a hidden field that populates it.
|
||||||
|
hidden_fields[unique_constraint_name] = HiddenField(default=default)
|
||||||
|
|
||||||
|
# Update `extra_kwargs` with any new options.
|
||||||
|
for key, value in uniqueness_extra_kwargs.items():
|
||||||
|
if key in extra_kwargs:
|
||||||
|
extra_kwargs[key].update(value)
|
||||||
|
else:
|
||||||
|
extra_kwargs[key] = value
|
||||||
|
|
||||||
|
return extra_kwargs, hidden_fields
|
||||||
|
|
||||||
|
def _get_model_fields(self, field_names, declared_fields, extra_kwargs):
|
||||||
|
"""
|
||||||
|
Returns all the model fields that are being mapped to by fields
|
||||||
|
on the serializer class.
|
||||||
|
Returned as a dict of 'model field name' -> 'model field'.
|
||||||
|
Used internally by `get_uniqueness_field_options`.
|
||||||
|
"""
|
||||||
|
model = getattr(self.Meta, 'model')
|
||||||
|
model_fields = {}
|
||||||
|
|
||||||
|
for field_name in field_names:
|
||||||
|
if field_name in declared_fields:
|
||||||
|
# If the field is declared on the serializer
|
||||||
|
field = declared_fields[field_name]
|
||||||
|
source = field.source or field_name
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
source = extra_kwargs[field_name]['source']
|
||||||
|
except KeyError:
|
||||||
|
source = field_name
|
||||||
|
|
||||||
|
if '.' in source or source == '*':
|
||||||
|
# Model fields will always have a simple source mapping,
|
||||||
|
# they can't be nested attribute lookups.
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
model_fields[source] = model._meta.get_field(source)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return model_fields
|
||||||
|
|
||||||
# Determine the validators to apply...
|
# Determine the validators to apply...
|
||||||
|
|
||||||
def get_validators(self):
|
def get_validators(self):
|
||||||
|
@ -882,361 +1293,6 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
|
|
||||||
# Determine the fields to apply...
|
|
||||||
|
|
||||||
def get_fields(self):
|
|
||||||
declared_fields = copy.deepcopy(self._declared_fields)
|
|
||||||
model = getattr(self.Meta, 'model')
|
|
||||||
depth = getattr(self.Meta, 'depth', 0)
|
|
||||||
|
|
||||||
# Retrieve metadata about fields & relationships on the model class.
|
|
||||||
info = model_meta.get_field_info(model)
|
|
||||||
field_names = self.get_field_names(declared_fields, info)
|
|
||||||
|
|
||||||
# Determine any extra field arguments and hidden fields that
|
|
||||||
# should be included
|
|
||||||
extra_kwargs = self.get_extra_kwargs()
|
|
||||||
extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs(
|
|
||||||
field_names, declared_fields, extra_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
# Now determine the fields that should be included on the serializer.
|
|
||||||
ret = OrderedDict()
|
|
||||||
for field_name in field_names:
|
|
||||||
if field_name in declared_fields:
|
|
||||||
# Field is explicitly declared on the class, use that.
|
|
||||||
ret[field_name] = declared_fields[field_name]
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Determine the serializer field class and keyword arguments.
|
|
||||||
field_cls, kwargs = self.build_field(field_name, info, model, depth)
|
|
||||||
|
|
||||||
# Populate any kwargs defined in `Meta.extra_kwargs`
|
|
||||||
kwargs = self.build_final_kwargs(kwargs, extra_kwargs, field_name)
|
|
||||||
|
|
||||||
# Create the serializer field.
|
|
||||||
ret[field_name] = field_cls(**kwargs)
|
|
||||||
|
|
||||||
# Add in any hidden fields.
|
|
||||||
ret.update(hidden_fields)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def build_field(self, field_name, info, model, depth):
|
|
||||||
if field_name in info.fields_and_pk:
|
|
||||||
# Create regular model fields.
|
|
||||||
model_field = info.fields_and_pk[field_name]
|
|
||||||
field_cls = self._field_mapping[model_field]
|
|
||||||
kwargs = get_field_kwargs(field_name, model_field)
|
|
||||||
if 'choices' in kwargs:
|
|
||||||
# Fields with choices get coerced into `ChoiceField`
|
|
||||||
# instead of using their regular typed field.
|
|
||||||
field_cls = ChoiceField
|
|
||||||
if not issubclass(field_cls, ModelField):
|
|
||||||
# `model_field` is only valid for the fallback case of
|
|
||||||
# `ModelField`, which is used when no other typed field
|
|
||||||
# matched to the model field.
|
|
||||||
kwargs.pop('model_field', None)
|
|
||||||
if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField):
|
|
||||||
# `allow_blank` is only valid for textual fields.
|
|
||||||
kwargs.pop('allow_blank', None)
|
|
||||||
|
|
||||||
elif field_name in info.relations:
|
|
||||||
# Create forward and reverse relationships.
|
|
||||||
relation_info = info.relations[field_name]
|
|
||||||
if depth:
|
|
||||||
field_cls = self._get_nested_class(depth, relation_info)
|
|
||||||
kwargs = get_nested_relation_kwargs(relation_info)
|
|
||||||
else:
|
|
||||||
field_cls = self._related_class
|
|
||||||
kwargs = get_relation_kwargs(field_name, relation_info)
|
|
||||||
# `view_name` is only valid for hyperlinked relationships.
|
|
||||||
if not issubclass(field_cls, HyperlinkedRelatedField):
|
|
||||||
kwargs.pop('view_name', None)
|
|
||||||
|
|
||||||
elif hasattr(model, field_name):
|
|
||||||
# Create a read only field for model methods and properties.
|
|
||||||
field_cls = ReadOnlyField
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
elif field_name == api_settings.URL_FIELD_NAME:
|
|
||||||
# Create the URL field.
|
|
||||||
field_cls = HyperlinkedIdentityField
|
|
||||||
kwargs = get_url_kwargs(model)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
'Field name `%s` is not valid for model `%s`.' %
|
|
||||||
(field_name, model.__class__.__name__)
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_cls, kwargs
|
|
||||||
|
|
||||||
def build_final_kwargs(self, kwargs, extra_kwargs, field_name):
|
|
||||||
"""
|
|
||||||
Include an 'extra_kwargs' that have been included for this field,
|
|
||||||
possibly removing any incompatible existing keyword arguments.
|
|
||||||
"""
|
|
||||||
extras = extra_kwargs.get(field_name, {})
|
|
||||||
|
|
||||||
if extras.get('read_only', False):
|
|
||||||
for attr in [
|
|
||||||
'required', 'default', 'allow_blank', 'allow_null',
|
|
||||||
'min_length', 'max_length', 'min_value', 'max_value',
|
|
||||||
'validators', 'queryset'
|
|
||||||
]:
|
|
||||||
kwargs.pop(attr, None)
|
|
||||||
|
|
||||||
if extras.get('default') and kwargs.get('required') is False:
|
|
||||||
kwargs.pop('required')
|
|
||||||
|
|
||||||
kwargs.update(extras)
|
|
||||||
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def _get_model_fields(self, field_names, declared_fields, extra_kwargs):
|
|
||||||
"""
|
|
||||||
Returns all the model fields that are being mapped to by fields
|
|
||||||
on the serializer class.
|
|
||||||
Returned as a dict of 'model field name' -> 'model field'.
|
|
||||||
Used internally by `get_uniqueness_field_options`.
|
|
||||||
"""
|
|
||||||
model = getattr(self.Meta, 'model')
|
|
||||||
model_fields = {}
|
|
||||||
|
|
||||||
for field_name in field_names:
|
|
||||||
if field_name in declared_fields:
|
|
||||||
# If the field is declared on the serializer
|
|
||||||
field = declared_fields[field_name]
|
|
||||||
source = field.source or field_name
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
source = extra_kwargs[field_name]['source']
|
|
||||||
except KeyError:
|
|
||||||
source = field_name
|
|
||||||
|
|
||||||
if '.' in source or source == '*':
|
|
||||||
# Model fields will always have a simple source mapping,
|
|
||||||
# they can't be nested attribute lookups.
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
model_fields[source] = model._meta.get_field(source)
|
|
||||||
except FieldDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return model_fields
|
|
||||||
|
|
||||||
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
|
|
||||||
"""
|
|
||||||
Return any additional field options that need to be included as a
|
|
||||||
result of uniqueness constraints on the model. This is returned as
|
|
||||||
a two-tuple of:
|
|
||||||
|
|
||||||
('dict of updated extra kwargs', 'mapping of hidden fields')
|
|
||||||
"""
|
|
||||||
model = getattr(self.Meta, 'model')
|
|
||||||
model_fields = self._get_model_fields(
|
|
||||||
field_names, declared_fields, extra_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
# Determine if we need any additional `HiddenField` or extra keyword
|
|
||||||
# arguments to deal with `unique_for` dates that are required to
|
|
||||||
# be in the input data in order to validate it.
|
|
||||||
unique_constraint_names = set()
|
|
||||||
|
|
||||||
for model_field in model_fields.values():
|
|
||||||
# Include each of the `unique_for_*` field names.
|
|
||||||
unique_constraint_names |= set([
|
|
||||||
model_field.unique_for_date,
|
|
||||||
model_field.unique_for_month,
|
|
||||||
model_field.unique_for_year
|
|
||||||
])
|
|
||||||
|
|
||||||
unique_constraint_names -= set([None])
|
|
||||||
|
|
||||||
# Include each of the `unique_together` field names,
|
|
||||||
# so long as all the field names are included on the serializer.
|
|
||||||
for parent_class in [model] + list(model._meta.parents.keys()):
|
|
||||||
for unique_together_list in parent_class._meta.unique_together:
|
|
||||||
if set(field_names).issuperset(set(unique_together_list)):
|
|
||||||
unique_constraint_names |= set(unique_together_list)
|
|
||||||
|
|
||||||
# Now we have all the field names that have uniqueness constraints
|
|
||||||
# applied, we can add the extra 'required=...' or 'default=...'
|
|
||||||
# arguments that are appropriate to these fields, or add a `HiddenField` for it.
|
|
||||||
hidden_fields = {}
|
|
||||||
uniqueness_extra_kwargs = {}
|
|
||||||
|
|
||||||
for unique_constraint_name in unique_constraint_names:
|
|
||||||
# Get the model field that is referred too.
|
|
||||||
unique_constraint_field = model._meta.get_field(unique_constraint_name)
|
|
||||||
|
|
||||||
if getattr(unique_constraint_field, 'auto_now_add', None):
|
|
||||||
default = CreateOnlyDefault(timezone.now)
|
|
||||||
elif getattr(unique_constraint_field, 'auto_now', None):
|
|
||||||
default = timezone.now
|
|
||||||
elif unique_constraint_field.has_default():
|
|
||||||
default = unique_constraint_field.default
|
|
||||||
else:
|
|
||||||
default = empty
|
|
||||||
|
|
||||||
if unique_constraint_name in model_fields:
|
|
||||||
# The corresponding field is present in the serializer
|
|
||||||
if default is empty:
|
|
||||||
uniqueness_extra_kwargs[unique_constraint_name] = {'required': True}
|
|
||||||
else:
|
|
||||||
uniqueness_extra_kwargs[unique_constraint_name] = {'default': default}
|
|
||||||
elif default is not empty:
|
|
||||||
# The corresponding field is not present in the,
|
|
||||||
# serializer. We have a default to use for it, so
|
|
||||||
# add in a hidden field that populates it.
|
|
||||||
hidden_fields[unique_constraint_name] = HiddenField(default=default)
|
|
||||||
|
|
||||||
# Update `extra_kwargs` with any new options.
|
|
||||||
for key, value in uniqueness_extra_kwargs.items():
|
|
||||||
if key in extra_kwargs:
|
|
||||||
extra_kwargs[key].update(value)
|
|
||||||
else:
|
|
||||||
extra_kwargs[key] = value
|
|
||||||
|
|
||||||
return extra_kwargs, hidden_fields
|
|
||||||
|
|
||||||
def get_extra_kwargs(self):
|
|
||||||
"""
|
|
||||||
Return a dictionary mapping field names to a dictionary of
|
|
||||||
additional keyword arguments.
|
|
||||||
"""
|
|
||||||
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
|
|
||||||
|
|
||||||
read_only_fields = getattr(self.Meta, 'read_only_fields', None)
|
|
||||||
if read_only_fields is not None:
|
|
||||||
for field_name in read_only_fields:
|
|
||||||
kwargs = extra_kwargs.get(field_name, {})
|
|
||||||
kwargs['read_only'] = True
|
|
||||||
extra_kwargs[field_name] = kwargs
|
|
||||||
|
|
||||||
# These are all pending deprecation.
|
|
||||||
write_only_fields = getattr(self.Meta, 'write_only_fields', None)
|
|
||||||
if write_only_fields is not None:
|
|
||||||
warnings.warn(
|
|
||||||
"The `Meta.write_only_fields` option is pending deprecation. "
|
|
||||||
"Use `Meta.extra_kwargs={<field_name>: {'write_only': True}}` instead.",
|
|
||||||
PendingDeprecationWarning,
|
|
||||||
stacklevel=3
|
|
||||||
)
|
|
||||||
for field_name in write_only_fields:
|
|
||||||
kwargs = extra_kwargs.get(field_name, {})
|
|
||||||
kwargs['write_only'] = True
|
|
||||||
extra_kwargs[field_name] = kwargs
|
|
||||||
|
|
||||||
view_name = getattr(self.Meta, 'view_name', None)
|
|
||||||
if view_name is not None:
|
|
||||||
warnings.warn(
|
|
||||||
"The `Meta.view_name` option is pending deprecation. "
|
|
||||||
"Use `Meta.extra_kwargs={'url': {'view_name': ...}}` instead.",
|
|
||||||
PendingDeprecationWarning,
|
|
||||||
stacklevel=3
|
|
||||||
)
|
|
||||||
kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {})
|
|
||||||
kwargs['view_name'] = view_name
|
|
||||||
extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs
|
|
||||||
|
|
||||||
lookup_field = getattr(self.Meta, 'lookup_field', None)
|
|
||||||
if lookup_field is not None:
|
|
||||||
warnings.warn(
|
|
||||||
"The `Meta.lookup_field` option is pending deprecation. "
|
|
||||||
"Use `Meta.extra_kwargs={'url': {'lookup_field': ...}}` instead.",
|
|
||||||
PendingDeprecationWarning,
|
|
||||||
stacklevel=3
|
|
||||||
)
|
|
||||||
kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {})
|
|
||||||
kwargs['lookup_field'] = lookup_field
|
|
||||||
extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs
|
|
||||||
|
|
||||||
return extra_kwargs
|
|
||||||
|
|
||||||
def get_field_names(self, declared_fields, info):
|
|
||||||
"""
|
|
||||||
Returns the list of all field names that should be created when
|
|
||||||
instantiating this serializer class. This is based on the default
|
|
||||||
set of fields, but also takes into account the `Meta.fields` or
|
|
||||||
`Meta.exclude` options if they have been specified.
|
|
||||||
"""
|
|
||||||
fields = getattr(self.Meta, 'fields', None)
|
|
||||||
exclude = getattr(self.Meta, 'exclude', None)
|
|
||||||
|
|
||||||
if fields and not isinstance(fields, (list, tuple)):
|
|
||||||
raise TypeError(
|
|
||||||
'The `fields` option must be a list or tuple. Got %s.' %
|
|
||||||
type(fields).__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
if exclude and not isinstance(exclude, (list, tuple)):
|
|
||||||
raise TypeError(
|
|
||||||
'The `exclude` option must be a list or tuple. Got %s.' %
|
|
||||||
type(exclude).__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not (fields and exclude), (
|
|
||||||
"Cannot set both 'fields' and 'exclude' options on "
|
|
||||||
"serializer {serializer_class}.".format(
|
|
||||||
serializer_class=self.__class__.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if fields is not None:
|
|
||||||
# Ensure that all declared fields have also been included in the
|
|
||||||
# `Meta.fields` option.
|
|
||||||
for field_name in declared_fields:
|
|
||||||
assert field_name in fields, (
|
|
||||||
"The field '{field_name}' was declared on serializer "
|
|
||||||
"{serializer_class}, but has not been included in the "
|
|
||||||
"'fields' option.".format(
|
|
||||||
field_name=field_name,
|
|
||||||
serializer_class=self.__class__.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return fields
|
|
||||||
|
|
||||||
# Use the default set of field names if `Meta.fields` is not specified.
|
|
||||||
fields = self.get_default_field_names(declared_fields, info)
|
|
||||||
|
|
||||||
if exclude is not None:
|
|
||||||
# If `Meta.exclude` is included, then remove those fields.
|
|
||||||
for field_name in exclude:
|
|
||||||
assert field_name in fields, (
|
|
||||||
"The field '{field_name}' was include on serializer "
|
|
||||||
"{serializer_class} in the 'exclude' option, but does "
|
|
||||||
"not match any model field.".format(
|
|
||||||
field_name=field_name,
|
|
||||||
serializer_class=self.__class__.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fields.remove(field_name)
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def get_default_field_names(self, declared_fields, model_info):
|
|
||||||
"""
|
|
||||||
Return the default list of field names that will be used if the
|
|
||||||
`Meta.fields` option is not specified.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
[model_info.pk.name] +
|
|
||||||
list(declared_fields.keys()) +
|
|
||||||
list(model_info.fields.keys()) +
|
|
||||||
list(model_info.forward_relations.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_nested_class(self, nested_depth, relation_info):
|
|
||||||
class NestedSerializer(ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = relation_info.related
|
|
||||||
depth = nested_depth - 1
|
|
||||||
|
|
||||||
return NestedSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedModelSerializer(ModelSerializer):
|
class HyperlinkedModelSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
@ -1246,7 +1302,7 @@ class HyperlinkedModelSerializer(ModelSerializer):
|
||||||
* A 'url' field is included instead of the 'id' field.
|
* A 'url' field is included instead of the 'id' field.
|
||||||
* Relationships to other instances are hyperlinks, instead of primary keys.
|
* Relationships to other instances are hyperlinks, instead of primary keys.
|
||||||
"""
|
"""
|
||||||
_related_class = HyperlinkedRelatedField
|
serializer_related_class = HyperlinkedRelatedField
|
||||||
|
|
||||||
def get_default_field_names(self, declared_fields, model_info):
|
def get_default_field_names(self, declared_fields, model_info):
|
||||||
"""
|
"""
|
||||||
|
@ -1260,10 +1316,17 @@ class HyperlinkedModelSerializer(ModelSerializer):
|
||||||
list(model_info.forward_relations.keys())
|
list(model_info.forward_relations.keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_nested_class(self, nested_depth, relation_info):
|
def build_nested_field(self, field_name, info, model, nested_depth):
|
||||||
|
"""
|
||||||
|
Create nested fields for forward and reverse relationships.
|
||||||
|
"""
|
||||||
|
relation_info = info.relations[field_name]
|
||||||
|
|
||||||
class NestedSerializer(HyperlinkedModelSerializer):
|
class NestedSerializer(HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = relation_info.related
|
model = relation_info.related
|
||||||
depth = nested_depth - 1
|
depth = nested_depth - 1
|
||||||
|
|
||||||
return NestedSerializer
|
field_cls = NestedSerializer
|
||||||
|
kwargs = get_nested_relation_kwargs(relation_info)
|
||||||
|
return field_cls, kwargs
|
||||||
|
|
Loading…
Reference in New Issue
Block a user