mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 04:50:12 +03:00
Merge branch 'master' into version-3.6
This commit is contained in:
commit
903f7fd5b5
|
@ -55,6 +55,8 @@ The `default` is not applied during partial update operations. In the partial up
|
|||
|
||||
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context).
|
||||
|
||||
When serializing the instance, default will be used if the the object attribute or dictionary key is not present in the instance.
|
||||
|
||||
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.
|
||||
|
||||
### `source`
|
||||
|
|
|
@ -69,7 +69,7 @@ The following attributes control the basic view behavior.
|
|||
|
||||
The following attributes are used to control pagination when used with list views.
|
||||
|
||||
* `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`.
|
||||
* `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`. Setting `pagination_class=None` will disable pagination on this view.
|
||||
|
||||
**Filtering**:
|
||||
|
||||
|
|
|
@ -443,7 +443,9 @@ class Field(object):
|
|||
try:
|
||||
return get_attribute(instance, self.source_attrs)
|
||||
except (KeyError, AttributeError) as exc:
|
||||
if not self.required and self.default is empty:
|
||||
if self.default is not empty:
|
||||
return self.get_default()
|
||||
if not self.required:
|
||||
raise SkipField()
|
||||
msg = (
|
||||
'Got {exc_type} when attempting to get a value for field '
|
||||
|
|
|
@ -22,12 +22,11 @@ from collections import OrderedDict, namedtuple
|
|||
from django.conf.urls import url
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from rest_framework import exceptions, renderers, views
|
||||
from rest_framework import views
|
||||
from rest_framework.compat import NoReverseMatch
|
||||
from rest_framework.renderers import BrowsableAPIRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.schemas import SchemaGenerator
|
||||
from rest_framework.schemas import SchemaGenerator, SchemaView
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
|
@ -276,6 +275,36 @@ class SimpleRouter(BaseRouter):
|
|||
return ret
|
||||
|
||||
|
||||
class APIRootView(views.APIView):
|
||||
"""
|
||||
The default basic root view for DefaultRouter
|
||||
"""
|
||||
_ignore_model_permissions = True
|
||||
exclude_from_schema = True
|
||||
api_root_dict = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Return a plain {"name": "hyperlink"} response.
|
||||
ret = OrderedDict()
|
||||
namespace = request.resolver_match.namespace
|
||||
for key, url_name in self.api_root_dict.items():
|
||||
if namespace:
|
||||
url_name = namespace + ':' + url_name
|
||||
try:
|
||||
ret[key] = reverse(
|
||||
url_name,
|
||||
args=args,
|
||||
kwargs=kwargs,
|
||||
request=request,
|
||||
format=kwargs.get('format', None)
|
||||
)
|
||||
except NoReverseMatch:
|
||||
# Don't bail out if eg. no list routes exist, only detail routes.
|
||||
continue
|
||||
|
||||
return Response(ret)
|
||||
|
||||
|
||||
class DefaultRouter(SimpleRouter):
|
||||
"""
|
||||
The default router extends the SimpleRouter, but also adds in a default
|
||||
|
@ -284,7 +313,9 @@ class DefaultRouter(SimpleRouter):
|
|||
include_root_view = True
|
||||
include_format_suffixes = True
|
||||
root_view_name = 'api-root'
|
||||
default_schema_renderers = [renderers.CoreJSONRenderer, BrowsableAPIRenderer]
|
||||
default_schema_renderers = None
|
||||
APIRootView = APIRootView
|
||||
APISchemaView = SchemaView
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'schema_title' in kwargs:
|
||||
|
@ -300,6 +331,14 @@ class DefaultRouter(SimpleRouter):
|
|||
self.schema_title = kwargs.pop('schema_title', None)
|
||||
self.schema_url = kwargs.pop('schema_url', None)
|
||||
self.schema_renderers = kwargs.pop('schema_renderers', self.default_schema_renderers)
|
||||
if self.default_schema_renderers:
|
||||
warnings.warn(
|
||||
"The 'DefaultRouter.default_schema_renderers' is pending "
|
||||
"deprecation. You should override "
|
||||
"'DefaultRouter.APISchemaView' instead.",
|
||||
PendingDeprecationWarning
|
||||
)
|
||||
|
||||
if 'root_renderers' in kwargs:
|
||||
self.root_renderers = kwargs.pop('root_renderers')
|
||||
else:
|
||||
|
@ -310,25 +349,16 @@ class DefaultRouter(SimpleRouter):
|
|||
"""
|
||||
Return a schema root view.
|
||||
"""
|
||||
schema_renderers = self.schema_renderers
|
||||
schema_generator = SchemaGenerator(
|
||||
title=self.schema_title,
|
||||
url=self.schema_url,
|
||||
patterns=api_urls
|
||||
)
|
||||
|
||||
class APISchemaView(views.APIView):
|
||||
_ignore_model_permissions = True
|
||||
exclude_from_schema = True
|
||||
renderer_classes = schema_renderers
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
schema = schema_generator.get_schema(request)
|
||||
if schema is None:
|
||||
raise exceptions.PermissionDenied()
|
||||
return Response(schema)
|
||||
|
||||
return APISchemaView.as_view()
|
||||
return self.APISchemaView.as_view(
|
||||
renderer_classes=self.schema_renderers,
|
||||
schema_generator=schema_generator,
|
||||
)
|
||||
|
||||
def get_api_root_view(self, api_urls=None):
|
||||
"""
|
||||
|
@ -339,32 +369,7 @@ class DefaultRouter(SimpleRouter):
|
|||
for prefix, viewset, basename in self.registry:
|
||||
api_root_dict[prefix] = list_name.format(basename=basename)
|
||||
|
||||
class APIRootView(views.APIView):
|
||||
_ignore_model_permissions = True
|
||||
exclude_from_schema = True
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Return a plain {"name": "hyperlink"} response.
|
||||
ret = OrderedDict()
|
||||
namespace = request.resolver_match.namespace
|
||||
for key, url_name in api_root_dict.items():
|
||||
if namespace:
|
||||
url_name = namespace + ':' + url_name
|
||||
try:
|
||||
ret[key] = reverse(
|
||||
url_name,
|
||||
args=args,
|
||||
kwargs=kwargs,
|
||||
request=request,
|
||||
format=kwargs.get('format', None)
|
||||
)
|
||||
except NoReverseMatch:
|
||||
# Don't bail out if eg. no list routes exist, only detail routes.
|
||||
continue
|
||||
|
||||
return Response(ret)
|
||||
|
||||
return APIRootView.as_view()
|
||||
return self.APIRootView.as_view(api_root_dict=api_root_dict)
|
||||
|
||||
def get_urls(self):
|
||||
"""
|
||||
|
|
|
@ -665,28 +665,38 @@ class SchemaGenerator(object):
|
|||
return named_path_components + [action]
|
||||
|
||||
|
||||
class SchemaView(APIView):
|
||||
_ignore_model_permissions = True
|
||||
exclude_from_schema = True
|
||||
renderer_classes = None
|
||||
schema_generator = None
|
||||
public = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SchemaView, self).__init__(*args, **kwargs)
|
||||
if self.renderer_classes is None:
|
||||
if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
|
||||
self.renderer_classes = [
|
||||
renderers.CoreJSONRenderer,
|
||||
renderers.BrowsableAPIRenderer,
|
||||
]
|
||||
else:
|
||||
self.renderer_classes = [renderers.CoreJSONRenderer]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
schema = self.schema_generator.get_schema(request, self.public)
|
||||
if schema is None:
|
||||
raise exceptions.PermissionDenied()
|
||||
return Response(schema)
|
||||
|
||||
|
||||
def get_schema_view(title=None, url=None, description=None, urlconf=None, renderer_classes=None, public=False):
|
||||
"""
|
||||
Return a schema view.
|
||||
"""
|
||||
generator = SchemaGenerator(title=title, url=url, description=description, urlconf=urlconf)
|
||||
if renderer_classes is None:
|
||||
if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
|
||||
rclasses = [renderers.CoreJSONRenderer, renderers.BrowsableAPIRenderer]
|
||||
else:
|
||||
rclasses = [renderers.CoreJSONRenderer]
|
||||
else:
|
||||
rclasses = renderer_classes
|
||||
|
||||
class SchemaView(APIView):
|
||||
_ignore_model_permissions = True
|
||||
exclude_from_schema = True
|
||||
renderer_classes = rclasses
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
schema = generator.get_schema(request, public)
|
||||
if schema is None:
|
||||
raise exceptions.PermissionDenied()
|
||||
return Response(schema)
|
||||
|
||||
return SchemaView.as_view()
|
||||
return SchemaView.as_view(
|
||||
renderer_classes=renderer_classes,
|
||||
schema_generator=generator,
|
||||
public=public,
|
||||
)
|
||||
|
|
|
@ -372,36 +372,44 @@ class TestNotRequiredOutput:
|
|||
serializer.save()
|
||||
assert serializer.data == {'included': 'abc'}
|
||||
|
||||
def test_default_required_output_for_dict(self):
|
||||
"""
|
||||
'default="something"' should require dictionary key.
|
||||
|
||||
We need to handle this as the field will have an implicit
|
||||
'required=False', but it should still have a value.
|
||||
"""
|
||||
class TestDefaultOutput:
|
||||
def setup(self):
|
||||
class ExampleSerializer(serializers.Serializer):
|
||||
omitted = serializers.CharField(default='abc')
|
||||
included = serializers.CharField()
|
||||
has_default = serializers.CharField(default='x')
|
||||
has_default_callable = serializers.CharField(default=lambda: 'y')
|
||||
no_default = serializers.CharField()
|
||||
self.Serializer = ExampleSerializer
|
||||
|
||||
serializer = ExampleSerializer({'included': 'abc'})
|
||||
with pytest.raises(KeyError):
|
||||
serializer.data
|
||||
|
||||
def test_default_required_output_for_object(self):
|
||||
def test_default_used_for_dict(self):
|
||||
"""
|
||||
'default="something"' should require object attribute.
|
||||
|
||||
We need to handle this as the field will have an implicit
|
||||
'required=False', but it should still have a value.
|
||||
'default="something"' should be used if dictionary key is missing from input.
|
||||
"""
|
||||
class ExampleSerializer(serializers.Serializer):
|
||||
omitted = serializers.CharField(default='abc')
|
||||
included = serializers.CharField()
|
||||
serializer = self.Serializer({'no_default': 'abc'})
|
||||
assert serializer.data == {'has_default': 'x', 'has_default_callable': 'y', 'no_default': 'abc'}
|
||||
|
||||
instance = MockObject(included='abc')
|
||||
serializer = ExampleSerializer(instance)
|
||||
with pytest.raises(AttributeError):
|
||||
serializer.data
|
||||
def test_default_used_for_object(self):
|
||||
"""
|
||||
'default="something"' should be used if object attribute is missing from input.
|
||||
"""
|
||||
instance = MockObject(no_default='abc')
|
||||
serializer = self.Serializer(instance)
|
||||
assert serializer.data == {'has_default': 'x', 'has_default_callable': 'y', 'no_default': 'abc'}
|
||||
|
||||
def test_default_not_used_when_in_dict(self):
|
||||
"""
|
||||
'default="something"' should not be used if dictionary key is present in input.
|
||||
"""
|
||||
serializer = self.Serializer({'has_default': 'def', 'has_default_callable': 'ghi', 'no_default': 'abc'})
|
||||
assert serializer.data == {'has_default': 'def', 'has_default_callable': 'ghi', 'no_default': 'abc'}
|
||||
|
||||
def test_default_not_used_when_in_object(self):
|
||||
"""
|
||||
'default="something"' should not be used if object attribute is present in input.
|
||||
"""
|
||||
instance = MockObject(has_default='def', has_default_callable='ghi', no_default='abc')
|
||||
serializer = self.Serializer(instance)
|
||||
assert serializer.data == {'has_default': 'def', 'has_default_callable': 'ghi', 'no_default': 'abc'}
|
||||
|
||||
|
||||
class TestCacheSerializerData:
|
||||
|
|
Loading…
Reference in New Issue
Block a user