mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Merge branch 'master' into display-raw-data
This commit is contained in:
commit
19f9adacb2
|
@ -73,7 +73,7 @@ The following attributes control the basic view behavior.
|
|||
|
||||
**Pagination**:
|
||||
|
||||
The following attibutes are used to control pagination when used with list views.
|
||||
The following attributes are used to control pagination when used with list views.
|
||||
|
||||
* `paginate_by` - The size of pages to use with paginated data. If set to `None` then pagination is turned off. If unset this uses the same value as the `PAGINATE_BY` setting, which defaults to `None`.
|
||||
* `paginate_by_param` - The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
|
||||
|
@ -135,7 +135,7 @@ For example:
|
|||
|
||||
#### `get_paginate_by(self)`
|
||||
|
||||
Returns the page size to use with pagination. By default this uses the `paginate_by` attribute, and may be overridden by the cient if the `paginate_by_param` attribute is set.
|
||||
Returns the page size to use with pagination. By default this uses the `paginate_by` attribute, and may be overridden by the client if the `paginate_by_param` attribute is set.
|
||||
|
||||
You may want to override this method to provide more complex behavior such as modifying page sizes based on the media type of the response.
|
||||
|
||||
|
|
|
@ -85,11 +85,12 @@ We could now use our pagination serializer in a view like this.
|
|||
|
||||
The generic class based views `ListAPIView` and `ListCreateAPIView` provide pagination of the returned querysets by default. You can customise this behaviour by altering the pagination style, by modifying the default number of results, by allowing clients to override the page size using a query parameter, or by turning pagination off completely.
|
||||
|
||||
The default pagination style may be set globally, using the `DEFAULT_PAGINATION_SERIALIZER_CLASS`, `PAGINATE_BY` and `PAGINATE_BY_PARAM` settings. For example.
|
||||
The default pagination style may be set globally, using the `DEFAULT_PAGINATION_SERIALIZER_CLASS`, `PAGINATE_BY`, `PAGINATE_BY_PARAM`, and `MAX_PAGINATE_BY` settings. For example.
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'PAGINATE_BY': 10,
|
||||
'PAGINATE_BY_PARAM': 'page_size'
|
||||
'PAGINATE_BY': 10, # Default to 10
|
||||
'PAGINATE_BY_PARAM': 'page_size', # Allow client to override, using `?page_size=xxx`.
|
||||
'MAX_PAGINATE_BY': 100 # Maximum limit allowed when using `?page_size=xxx`.
|
||||
}
|
||||
|
||||
You can also set the pagination style on a per-view basis, using the `ListAPIView` generic class-based view.
|
||||
|
@ -99,6 +100,7 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie
|
|||
serializer_class = ExampleModelSerializer
|
||||
paginate_by = 10
|
||||
paginate_by_param = 'page_size'
|
||||
max_paginate_by = 100
|
||||
|
||||
Note that using a `paginate_by` value of `None` will turn off pagination for the view.
|
||||
|
||||
|
|
|
@ -212,6 +212,10 @@ The following third party packages are also available.
|
|||
|
||||
The [DRF Any Permissions][drf-any-permissions] packages provides a different permission behavior in contrast to REST framework. Instead of all specified permissions being required, only one of the given permissions has to be true in order to get access to the view.
|
||||
|
||||
## Composed Permissions
|
||||
|
||||
The [Composed Permissions][composed-permissions] package provides a simple way to define complex and multi-depth (with logic operators) permission objects, using small and reusable components.
|
||||
|
||||
[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
|
||||
[authentication]: authentication.md
|
||||
[throttling]: throttling.md
|
||||
|
@ -222,3 +226,4 @@ The [DRF Any Permissions][drf-any-permissions] packages provides a different per
|
|||
[2.2-announcement]: ../topics/2.2-announcement.md
|
||||
[filtering]: filtering.md
|
||||
[drf-any-permissions]: https://github.com/kevin-brown/drf-any-permissions
|
||||
[composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions
|
||||
|
|
|
@ -117,7 +117,7 @@ For more information see the [browser enhancements documentation].
|
|||
|
||||
# Standard HttpRequest attributes
|
||||
|
||||
As REST framework's `Request` extends Django's `HttpRequest`, all the other standard attributes and methods are also available. For example the `request.META` dictionary is available as normal.
|
||||
As REST framework's `Request` extends Django's `HttpRequest`, all the other standard attributes and methods are also available. For example the `request.META` and `request.session` dictionaries are available as normal.
|
||||
|
||||
Note that due to implementation reasons the `Request` class does not inherit from `HttpRequest` class, but instead extends the class using composition.
|
||||
|
||||
|
|
|
@ -127,6 +127,35 @@ Default: `None`
|
|||
|
||||
The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If set to `None`, clients may not override the default page size.
|
||||
|
||||
For example, given the following settings:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'PAGINATE_BY': 10,
|
||||
'PAGINATE_BY_PARAM': 'page_size',
|
||||
}
|
||||
|
||||
A client would be able to modify the pagination size by using the `page_size` query parameter. For example:
|
||||
|
||||
GET http://example.com/api/accounts?page_size=25
|
||||
|
||||
Default: `None`
|
||||
|
||||
#### MAX_PAGINATE_BY
|
||||
|
||||
The maximum page size to allow when the page size is specified by the client. If set to `None`, then no maximum limit is applied.
|
||||
|
||||
For example, given the following settings:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'PAGINATE_BY': 10,
|
||||
'PAGINATE_BY_PARAM': 'page_size',
|
||||
'MAX_PAGINATE_BY': 100
|
||||
}
|
||||
|
||||
A client request like the following would return a paginated list of up to 100 items.
|
||||
|
||||
GET http://example.com/api/accounts?page_size=999
|
||||
|
||||
Default: `None`
|
||||
|
||||
---
|
||||
|
|
|
@ -70,6 +70,13 @@ Or, if you're using the `@api_view` decorator with function based views.
|
|||
|
||||
The throttle classes provided by REST framework use Django's cache backend. You should make sure that you've set appropriate [cache settings][cache-setting]. The default value of `LocMemCache` backend should be okay for simple setups. See Django's [cache documentation][cache-docs] for more details.
|
||||
|
||||
If you need to use a cache other than `'default'`, you can do so by creating a custom throttle class and setting the `cache` attribute. For example:
|
||||
|
||||
class CustomAnonRateThrottle(AnonRateThrottle):
|
||||
cache = get_cache('alternate')
|
||||
|
||||
You'll need to rememeber to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute.
|
||||
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
|
|
@ -200,7 +200,7 @@ To run the tests against all supported configurations, first install [the tox te
|
|||
|
||||
## Support
|
||||
|
||||
For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.freenode.net`, or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
|
||||
For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.freenode.net`, search [the IRC archives][botbot], or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
|
||||
|
||||
[Paid support is available][paid-support] from [DabApps][dabapps], and can include work on REST framework core, or support with building your REST framework API. Please [contact DabApps][contact-dabapps] if you'd like to discuss commercial support options.
|
||||
|
||||
|
@ -307,6 +307,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[tox]: http://testrun.org/tox/latest/
|
||||
|
||||
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||
[botbot]: https://botbot.me/freenode/restframework/
|
||||
[stack-overflow]: http://stackoverflow.com/
|
||||
[django-rest-framework-tag]: http://stackoverflow.com/questions/tagged/django-rest-framework
|
||||
[django-tag]: http://stackoverflow.com/questions/tagged/django
|
||||
|
|
|
@ -163,6 +163,9 @@ The following people have helped make REST framework great.
|
|||
* Krzysztof Jurewicz - [krzysiekj]
|
||||
* Eric Buehl - [ericbuehl]
|
||||
* Kristian Øllegaard - [kristianoellegaard]
|
||||
* Alexander Akhmetov - [alexander-akhmetov]
|
||||
* Andrey Antukh - [niwibe]
|
||||
* Mathieu Pillard - [diox]
|
||||
|
||||
Many thanks to everyone who's contributed to the project.
|
||||
|
||||
|
@ -362,3 +365,6 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
|
|||
[krzysiekj]: https://github.com/krzysiekj
|
||||
[ericbuehl]: https://github.com/ericbuehl
|
||||
[kristianoellegaard]: https://github.com/kristianoellegaard
|
||||
[alexander-akhmetov]: https://github.com/alexander-akhmetov
|
||||
[niwibe]: https://github.com/niwibe
|
||||
[diox]: https://github.com/diox
|
||||
|
|
|
@ -43,9 +43,12 @@ You can determine your currently installed version using `pip freeze`:
|
|||
### Master
|
||||
|
||||
* Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.
|
||||
* Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute.
|
||||
* Added `cache` attribute to throttles to allow overriding of default cache.
|
||||
* Bugfix: `required=True` argument fixed for boolean serializer fields.
|
||||
* Bugfix: `client.force_authenticate(None)` should also clear session info if it exists.
|
||||
* Bugfix: Client sending emptry string instead of file now clears `FileField`.
|
||||
* Bugfix: Empty values on ChoiceFields with `required=False` now consistently return `None`.
|
||||
|
||||
### 2.3.7
|
||||
|
||||
|
|
|
@ -514,6 +514,11 @@ class ChoiceField(WritableField):
|
|||
return True
|
||||
return False
|
||||
|
||||
def from_native(self, value):
|
||||
if value in validators.EMPTY_VALUES:
|
||||
return None
|
||||
return super(ChoiceField, self).from_native(value)
|
||||
|
||||
|
||||
class EmailField(CharField):
|
||||
type_name = 'EmailField'
|
||||
|
|
|
@ -14,13 +14,15 @@ from rest_framework.settings import api_settings
|
|||
import warnings
|
||||
|
||||
|
||||
def strict_positive_int(integer_string):
|
||||
def strict_positive_int(integer_string, cutoff=None):
|
||||
"""
|
||||
Cast a string to a strictly positive integer.
|
||||
"""
|
||||
ret = int(integer_string)
|
||||
if ret <= 0:
|
||||
raise ValueError()
|
||||
if cutoff:
|
||||
ret = min(ret, cutoff)
|
||||
return ret
|
||||
|
||||
def get_object_or_404(queryset, **filter_kwargs):
|
||||
|
@ -56,6 +58,7 @@ class GenericAPIView(views.APIView):
|
|||
# Pagination settings
|
||||
paginate_by = api_settings.PAGINATE_BY
|
||||
paginate_by_param = api_settings.PAGINATE_BY_PARAM
|
||||
max_paginate_by = api_settings.MAX_PAGINATE_BY
|
||||
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
|
||||
page_kwarg = 'page'
|
||||
|
||||
|
@ -205,9 +208,11 @@ class GenericAPIView(views.APIView):
|
|||
PendingDeprecationWarning, stacklevel=2)
|
||||
|
||||
if self.paginate_by_param:
|
||||
query_params = self.request.QUERY_PARAMS
|
||||
try:
|
||||
return int(query_params[self.paginate_by_param])
|
||||
return strict_positive_int(
|
||||
self.request.QUERY_PARAMS[self.paginate_by_param],
|
||||
cutoff=self.max_paginate_by
|
||||
)
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ DEFAULTS = {
|
|||
),
|
||||
'DEFAULT_THROTTLE_CLASSES': (
|
||||
),
|
||||
|
||||
'DEFAULT_CONTENT_NEGOTIATION_CLASS':
|
||||
'rest_framework.negotiation.DefaultContentNegotiation',
|
||||
|
||||
|
@ -68,15 +67,16 @@ DEFAULTS = {
|
|||
# Pagination
|
||||
'PAGINATE_BY': None,
|
||||
'PAGINATE_BY_PARAM': None,
|
||||
|
||||
# View configuration
|
||||
'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
|
||||
'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',
|
||||
'MAX_PAGINATE_BY': None,
|
||||
|
||||
# Authentication
|
||||
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
|
||||
'UNAUTHENTICATED_TOKEN': None,
|
||||
|
||||
# View configuration
|
||||
'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
|
||||
'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',
|
||||
|
||||
# Testing
|
||||
'TEST_REQUEST_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.MultiPartRenderer',
|
||||
|
|
|
@ -688,6 +688,14 @@ class ChoiceFieldTests(TestCase):
|
|||
f = serializers.ChoiceField(required=False, choices=self.SAMPLE_CHOICES)
|
||||
self.assertEqual(f.choices, models.fields.BLANK_CHOICE_DASH + self.SAMPLE_CHOICES)
|
||||
|
||||
def test_from_native_empty(self):
|
||||
"""
|
||||
Make sure from_native() returns None on empty param.
|
||||
"""
|
||||
f = serializers.ChoiceField(choices=self.SAMPLE_CHOICES)
|
||||
result = f.from_native('')
|
||||
self.assertEqual(result, None)
|
||||
|
||||
|
||||
class EmailFieldTests(TestCase):
|
||||
"""
|
||||
|
|
|
@ -42,6 +42,16 @@ class PaginateByParamView(generics.ListAPIView):
|
|||
paginate_by_param = 'page_size'
|
||||
|
||||
|
||||
class MaxPaginateByView(generics.ListAPIView):
|
||||
"""
|
||||
View for testing custom max_paginate_by usage
|
||||
"""
|
||||
model = BasicModel
|
||||
paginate_by = 3
|
||||
max_paginate_by = 5
|
||||
paginate_by_param = 'page_size'
|
||||
|
||||
|
||||
class IntegrationTestPagination(TestCase):
|
||||
"""
|
||||
Integration tests for paginated list views.
|
||||
|
@ -313,6 +323,43 @@ class TestCustomPaginateByParam(TestCase):
|
|||
self.assertEqual(response.data['results'], self.data[:5])
|
||||
|
||||
|
||||
class TestMaxPaginateByParam(TestCase):
|
||||
"""
|
||||
Tests for list views with max_paginate_by kwarg
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create 13 BasicModel instances.
|
||||
"""
|
||||
for i in range(13):
|
||||
BasicModel(text=i).save()
|
||||
self.objects = BasicModel.objects
|
||||
self.data = [
|
||||
{'id': obj.id, 'text': obj.text}
|
||||
for obj in self.objects.all()
|
||||
]
|
||||
self.view = MaxPaginateByView.as_view()
|
||||
|
||||
def test_max_paginate_by(self):
|
||||
"""
|
||||
If max_paginate_by is set, it should limit page size for the view.
|
||||
"""
|
||||
request = factory.get('/?page_size=10')
|
||||
response = self.view(request).render()
|
||||
self.assertEqual(response.data['count'], 13)
|
||||
self.assertEqual(response.data['results'], self.data[:5])
|
||||
|
||||
def test_max_paginate_by_without_page_size_param(self):
|
||||
"""
|
||||
If max_paginate_by is set, but client does not specifiy page_size,
|
||||
standard `paginate_by` behavior should be used.
|
||||
"""
|
||||
request = factory.get('/')
|
||||
response = self.view(request).render()
|
||||
self.assertEqual(response.data['results'], self.data[:3])
|
||||
|
||||
|
||||
### Tests for context in pagination serializers
|
||||
|
||||
class CustomField(serializers.Field):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Provides various throttling policies.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from django.core.cache import cache
|
||||
from django.core.cache import cache as default_cache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from rest_framework.settings import api_settings
|
||||
import time
|
||||
|
@ -39,6 +39,7 @@ class SimpleRateThrottle(BaseThrottle):
|
|||
Previous request information used for throttling is stored in the cache.
|
||||
"""
|
||||
|
||||
cache = default_cache
|
||||
timer = time.time
|
||||
cache_format = 'throtte_%(scope)s_%(ident)s'
|
||||
scope = None
|
||||
|
@ -99,7 +100,7 @@ class SimpleRateThrottle(BaseThrottle):
|
|||
if self.key is None:
|
||||
return True
|
||||
|
||||
self.history = cache.get(self.key, [])
|
||||
self.history = self.cache.get(self.key, [])
|
||||
self.now = self.timer()
|
||||
|
||||
# Drop any requests from the history which have now passed the
|
||||
|
@ -116,7 +117,7 @@ class SimpleRateThrottle(BaseThrottle):
|
|||
into the cache.
|
||||
"""
|
||||
self.history.insert(0, self.now)
|
||||
cache.set(self.key, self.history, self.duration)
|
||||
self.cache.set(self.key, self.history, self.duration)
|
||||
return True
|
||||
|
||||
def throttle_failure(self):
|
||||
|
@ -151,7 +152,9 @@ class AnonRateThrottle(SimpleRateThrottle):
|
|||
if request.user.is_authenticated():
|
||||
return None # Only throttle unauthenticated requests.
|
||||
|
||||
ident = request.META.get('REMOTE_ADDR', None)
|
||||
ident = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if ident is None:
|
||||
ident = request.META.get('REMOTE_ADDR')
|
||||
|
||||
return self.cache_format % {
|
||||
'scope': self.scope,
|
||||
|
|
|
@ -8,8 +8,11 @@ def get_breadcrumbs(url):
|
|||
tuple of (name, url).
|
||||
"""
|
||||
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import APIView
|
||||
|
||||
view_name_func = api_settings.VIEW_NAME_FUNCTION
|
||||
|
||||
def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen):
|
||||
"""
|
||||
Add tuples of (name, url) to the breadcrumbs list,
|
||||
|
@ -28,8 +31,8 @@ def get_breadcrumbs(url):
|
|||
# Don't list the same view twice in a row.
|
||||
# Probably an optional trailing slash.
|
||||
if not seen or seen[-1] != view:
|
||||
instance = view.cls()
|
||||
name = instance.get_view_name()
|
||||
suffix = getattr(view, 'suffix', None)
|
||||
name = view_name_func(cls, suffix)
|
||||
breadcrumbs_list.insert(0, (name, prefix + url))
|
||||
seen.append(view)
|
||||
|
||||
|
|
|
@ -15,8 +15,14 @@ from rest_framework.settings import api_settings
|
|||
from rest_framework.utils import formatting
|
||||
|
||||
|
||||
def get_view_name(cls, suffix=None):
|
||||
name = cls.__name__
|
||||
def get_view_name(view_cls, suffix=None):
|
||||
"""
|
||||
Given a view class, return a textual name to represent the view.
|
||||
This name is used in the browsable API, and in OPTIONS responses.
|
||||
|
||||
This function is the default for the `VIEW_NAME_FUNCTION` setting.
|
||||
"""
|
||||
name = view_cls.__name__
|
||||
name = formatting.remove_trailing_string(name, 'View')
|
||||
name = formatting.remove_trailing_string(name, 'ViewSet')
|
||||
name = formatting.camelcase_to_spaces(name)
|
||||
|
@ -25,17 +31,56 @@ def get_view_name(cls, suffix=None):
|
|||
|
||||
return name
|
||||
|
||||
def get_view_description(cls, html=False):
|
||||
description = cls.__doc__ or ''
|
||||
def get_view_description(view_cls, html=False):
|
||||
"""
|
||||
Given a view class, return a textual description to represent the view.
|
||||
This name is used in the browsable API, and in OPTIONS responses.
|
||||
|
||||
This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.
|
||||
"""
|
||||
description = view_cls.__doc__ or ''
|
||||
description = formatting.dedent(smart_text(description))
|
||||
if html:
|
||||
return formatting.markup_description(description)
|
||||
return description
|
||||
|
||||
|
||||
class APIView(View):
|
||||
settings = api_settings
|
||||
def exception_handler(exc):
|
||||
"""
|
||||
Returns the response that should be used for any given exception.
|
||||
|
||||
By default we handle the REST framework `APIException`, and also
|
||||
Django's builtin `Http404` and `PermissionDenied` exceptions.
|
||||
|
||||
Any unhandled exceptions may return `None`, which will cause a 500 error
|
||||
to be raised.
|
||||
"""
|
||||
if isinstance(exc, exceptions.APIException):
|
||||
headers = {}
|
||||
if getattr(exc, 'auth_header', None):
|
||||
headers['WWW-Authenticate'] = exc.auth_header
|
||||
if getattr(exc, 'wait', None):
|
||||
headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
|
||||
|
||||
return Response({'detail': exc.detail},
|
||||
status=exc.status_code,
|
||||
headers=headers)
|
||||
|
||||
elif isinstance(exc, Http404):
|
||||
return Response({'detail': 'Not found'},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
elif isinstance(exc, PermissionDenied):
|
||||
return Response({'detail': 'Permission denied'},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Note: Unhandled exceptions will raise a 500 error.
|
||||
return None
|
||||
|
||||
|
||||
class APIView(View):
|
||||
|
||||
# The following policies may be set at either globally, or per-view.
|
||||
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
|
||||
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
|
||||
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||
|
@ -43,6 +88,9 @@ class APIView(View):
|
|||
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
|
||||
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
|
||||
|
||||
# Allow dependancy injection of other settings to make testing easier.
|
||||
settings = api_settings
|
||||
|
||||
@classmethod
|
||||
def as_view(cls, **initkwargs):
|
||||
"""
|
||||
|
@ -133,7 +181,7 @@ class APIView(View):
|
|||
Return the view name, as used in OPTIONS responses and in the
|
||||
browsable API.
|
||||
"""
|
||||
func = api_settings.VIEW_NAME_FUNCTION
|
||||
func = self.settings.VIEW_NAME_FUNCTION
|
||||
return func(self.__class__, getattr(self, 'suffix', None))
|
||||
|
||||
def get_view_description(self, html=False):
|
||||
|
@ -141,7 +189,7 @@ class APIView(View):
|
|||
Return some descriptive text for the view, as used in OPTIONS responses
|
||||
and in the browsable API.
|
||||
"""
|
||||
func = api_settings.VIEW_DESCRIPTION_FUNCTION
|
||||
func = self.settings.VIEW_DESCRIPTION_FUNCTION
|
||||
return func(self.__class__, html)
|
||||
|
||||
# API policy instantiation methods
|
||||
|
@ -303,33 +351,23 @@ class APIView(View):
|
|||
Handle any exception that occurs, by returning an appropriate response,
|
||||
or re-raising the error.
|
||||
"""
|
||||
if isinstance(exc, exceptions.Throttled) and exc.wait is not None:
|
||||
# Throttle wait header
|
||||
self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
|
||||
|
||||
if isinstance(exc, (exceptions.NotAuthenticated,
|
||||
exceptions.AuthenticationFailed)):
|
||||
# WWW-Authenticate header for 401 responses, else coerce to 403
|
||||
auth_header = self.get_authenticate_header(self.request)
|
||||
|
||||
if auth_header:
|
||||
self.headers['WWW-Authenticate'] = auth_header
|
||||
exc.auth_header = auth_header
|
||||
else:
|
||||
exc.status_code = status.HTTP_403_FORBIDDEN
|
||||
|
||||
if isinstance(exc, exceptions.APIException):
|
||||
return Response({'detail': exc.detail},
|
||||
status=exc.status_code,
|
||||
exception=True)
|
||||
elif isinstance(exc, Http404):
|
||||
return Response({'detail': 'Not found'},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
exception=True)
|
||||
elif isinstance(exc, PermissionDenied):
|
||||
return Response({'detail': 'Permission denied'},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
exception=True)
|
||||
raise
|
||||
response = exception_handler(exc)
|
||||
|
||||
if response is None:
|
||||
raise
|
||||
|
||||
response.exception = True
|
||||
return response
|
||||
|
||||
# Note: session based authentication is explicitly CSRF validated,
|
||||
# all other authentication is CSRF exempt.
|
||||
|
|
Loading…
Reference in New Issue
Block a user