mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
7a9db57eaf
* Propagate 'default' from model_field to serializer field Fix #7469. Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com> * updated field default on serializer according to openapi generation and added that to options action response * added notes regarding default value propagation from model to serializer field * updated note * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md --------- Co-authored-by: John Parton <john.parton.iv@gmail.com> Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com> Co-authored-by: Rizwan Shaikh <rshaikh@ces-ltd.com> Co-authored-by: Asif Saif Uddin <auvipy@gmail.com>
157 lines
5.9 KiB
Python
157 lines
5.9 KiB
Python
"""
|
|
The metadata API is used to allow customization of how `OPTIONS` requests
|
|
are handled. We currently provide a single default implementation that returns
|
|
some fairly ad-hoc information about the view.
|
|
|
|
Future implementations might use JSON schema or other definitions in order
|
|
to return this information in a more standardized way.
|
|
"""
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.http import Http404
|
|
from django.utils.encoding import force_str
|
|
|
|
from rest_framework import exceptions, serializers
|
|
from rest_framework.fields import empty
|
|
from rest_framework.request import clone_request
|
|
from rest_framework.utils.field_mapping import ClassLookupDict
|
|
|
|
|
|
class BaseMetadata:
|
|
def determine_metadata(self, request, view):
|
|
"""
|
|
Return a dictionary of metadata about the view.
|
|
Used to return responses for OPTIONS requests.
|
|
"""
|
|
raise NotImplementedError(".determine_metadata() must be overridden.")
|
|
|
|
|
|
class SimpleMetadata(BaseMetadata):
|
|
"""
|
|
This is the default metadata implementation.
|
|
It returns an ad-hoc set of information about the view.
|
|
There are not any formalized standards for `OPTIONS` responses
|
|
for us to base this on.
|
|
"""
|
|
label_lookup = ClassLookupDict({
|
|
serializers.Field: 'field',
|
|
serializers.BooleanField: 'boolean',
|
|
serializers.CharField: 'string',
|
|
serializers.UUIDField: 'string',
|
|
serializers.URLField: 'url',
|
|
serializers.EmailField: 'email',
|
|
serializers.RegexField: 'regex',
|
|
serializers.SlugField: 'slug',
|
|
serializers.IntegerField: 'integer',
|
|
serializers.FloatField: 'float',
|
|
serializers.DecimalField: 'decimal',
|
|
serializers.DateField: 'date',
|
|
serializers.DateTimeField: 'datetime',
|
|
serializers.TimeField: 'time',
|
|
serializers.DurationField: 'duration',
|
|
serializers.ChoiceField: 'choice',
|
|
serializers.MultipleChoiceField: 'multiple choice',
|
|
serializers.FileField: 'file upload',
|
|
serializers.ImageField: 'image upload',
|
|
serializers.ListField: 'list',
|
|
serializers.DictField: 'nested object',
|
|
serializers.Serializer: 'nested object',
|
|
})
|
|
|
|
def determine_metadata(self, request, view):
|
|
metadata = {
|
|
"name": view.get_view_name(),
|
|
"description": view.get_view_description(),
|
|
"renders": [renderer.media_type for renderer in view.renderer_classes],
|
|
"parses": [parser.media_type for parser in view.parser_classes],
|
|
}
|
|
if hasattr(view, 'get_serializer'):
|
|
actions = self.determine_actions(request, view)
|
|
if actions:
|
|
metadata['actions'] = actions
|
|
return metadata
|
|
|
|
def determine_actions(self, request, view):
|
|
"""
|
|
For generic class based views we return information about
|
|
the fields that are accepted for 'PUT' and 'POST' methods.
|
|
"""
|
|
actions = {}
|
|
for method in {'PUT', 'POST'} & set(view.allowed_methods):
|
|
view.request = clone_request(request, method)
|
|
try:
|
|
# Test global permissions
|
|
if hasattr(view, 'check_permissions'):
|
|
view.check_permissions(view.request)
|
|
# Test object permissions
|
|
if method == 'PUT' and hasattr(view, 'get_object'):
|
|
view.get_object()
|
|
except (exceptions.APIException, PermissionDenied, Http404):
|
|
pass
|
|
else:
|
|
# If user has appropriate permissions for the view, include
|
|
# appropriate metadata about the fields that should be supplied.
|
|
serializer = view.get_serializer()
|
|
actions[method] = self.get_serializer_info(serializer)
|
|
finally:
|
|
view.request = request
|
|
|
|
return actions
|
|
|
|
def get_serializer_info(self, serializer):
|
|
"""
|
|
Given an instance of a serializer, return a dictionary of metadata
|
|
about its fields.
|
|
"""
|
|
if hasattr(serializer, 'child'):
|
|
# If this is a `ListSerializer` then we want to examine the
|
|
# underlying child serializer instance instead.
|
|
serializer = serializer.child
|
|
return {
|
|
field_name: self.get_field_info(field)
|
|
for field_name, field in serializer.fields.items()
|
|
if not isinstance(field, serializers.HiddenField)
|
|
}
|
|
|
|
def get_field_info(self, field):
|
|
"""
|
|
Given an instance of a serializer field, return a dictionary
|
|
of metadata about it.
|
|
"""
|
|
field_info = {
|
|
"type": self.label_lookup[field],
|
|
"required": getattr(field, "required", False),
|
|
}
|
|
|
|
attrs = [
|
|
'read_only', 'label', 'help_text',
|
|
'min_length', 'max_length',
|
|
'min_value', 'max_value',
|
|
'max_digits', 'decimal_places'
|
|
]
|
|
|
|
for attr in attrs:
|
|
value = getattr(field, attr, None)
|
|
if value is not None and value != '':
|
|
field_info[attr] = force_str(value, strings_only=True)
|
|
|
|
if getattr(field, 'child', None):
|
|
field_info['child'] = self.get_field_info(field.child)
|
|
elif getattr(field, 'fields', None):
|
|
field_info['children'] = self.get_serializer_info(field)
|
|
|
|
if (not field_info.get('read_only') and
|
|
not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
|
|
hasattr(field, 'choices')):
|
|
field_info['choices'] = [
|
|
{
|
|
'value': choice_value,
|
|
'display_name': force_str(choice_name, strings_only=True)
|
|
}
|
|
for choice_value, choice_name in field.choices.items()
|
|
]
|
|
|
|
if getattr(field, 'default', None) and field.default != empty and not callable(field.default):
|
|
field_info['default'] = field.default
|
|
|
|
return field_info
|