mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 05:04:31 +03:00
Drop ImmediateResponse
This commit is contained in:
parent
edd8f5963c
commit
73cc77553e
|
@ -1,9 +1,15 @@
|
|||
"""
|
||||
Handled exceptions raised by REST framework.
|
||||
|
||||
In addition Django's built in 403 and 404 exceptions are handled.
|
||||
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
|
||||
"""
|
||||
from djangorestframework import status
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = 'Malformed request'
|
||||
default_detail = 'Malformed request.'
|
||||
|
||||
def __init__(self, detail=None):
|
||||
self.detail = detail or self.default_detail
|
||||
|
@ -11,7 +17,7 @@ class ParseError(Exception):
|
|||
|
||||
class PermissionDenied(Exception):
|
||||
status_code = status.HTTP_403_FORBIDDEN
|
||||
default_detail = 'You do not have permission to access this resource'
|
||||
default_detail = 'You do not have permission to access this resource.'
|
||||
|
||||
def __init__(self, detail=None):
|
||||
self.detail = detail or self.default_detail
|
||||
|
@ -19,19 +25,30 @@ class PermissionDenied(Exception):
|
|||
|
||||
class MethodNotAllowed(Exception):
|
||||
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
default_detail = "Method '%s' not allowed"
|
||||
default_detail = "Method '%s' not allowed."
|
||||
|
||||
def __init__(self, method, detail):
|
||||
def __init__(self, method, detail=None):
|
||||
self.detail = (detail or self.default_detail) % method
|
||||
|
||||
|
||||
class UnsupportedMediaType(Exception):
|
||||
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
||||
default_detail = "Unsupported media type '%s' in request"
|
||||
default_detail = "Unsupported media type '%s' in request."
|
||||
|
||||
def __init__(self, media_type, detail=None):
|
||||
self.detail = (detail or self.default_detail) % media_type
|
||||
|
||||
# class Throttled(Exception):
|
||||
# def __init__(self, detail):
|
||||
# self.detail = detail
|
||||
|
||||
class Throttled(Exception):
|
||||
status_code = status.HTTP_429_TOO_MANY_REQUESTS
|
||||
default_detail = "Request was throttled. Expected available in %d seconds."
|
||||
|
||||
def __init__(self, wait, detail=None):
|
||||
import math
|
||||
self.detail = (detail or self.default_detail) % int(math.ceil(wait))
|
||||
|
||||
|
||||
REST_FRAMEWORK_EXCEPTIONS = (
|
||||
ParseError, PermissionDenied, MethodNotAllowed,
|
||||
UnsupportedMediaType, Throttled
|
||||
)
|
||||
|
|
|
@ -6,9 +6,7 @@ Permission behavior is provided by mixing the :class:`mixins.PermissionsMixin` c
|
|||
"""
|
||||
|
||||
from django.core.cache import cache
|
||||
from djangorestframework import status
|
||||
from djangorestframework.exceptions import PermissionDenied
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework.exceptions import PermissionDenied, Throttled
|
||||
import time
|
||||
|
||||
__all__ = (
|
||||
|
@ -24,11 +22,6 @@ __all__ = (
|
|||
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
||||
|
||||
|
||||
_503_SERVICE_UNAVAILABLE = ImmediateResponse(
|
||||
{'detail': 'request was throttled'},
|
||||
status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
|
||||
|
||||
class BasePermission(object):
|
||||
"""
|
||||
A base class from which all permission classes should inherit.
|
||||
|
@ -192,7 +185,7 @@ class BaseThrottle(BasePermission):
|
|||
"""
|
||||
self.history.insert(0, self.now)
|
||||
cache.set(self.key, self.history, self.duration)
|
||||
header = 'status=SUCCESS; next=%s sec' % self.next()
|
||||
header = 'status=SUCCESS; next=%.2f sec' % self.next()
|
||||
self.view.headers['X-Throttle'] = header
|
||||
|
||||
def throttle_failure(self):
|
||||
|
@ -200,9 +193,10 @@ class BaseThrottle(BasePermission):
|
|||
Called when a request to the API has failed due to throttling.
|
||||
Raises a '503 service unavailable' response.
|
||||
"""
|
||||
header = 'status=FAILURE; next=%s sec' % self.next()
|
||||
wait = self.next()
|
||||
header = 'status=FAILURE; next=%.2f sec' % wait
|
||||
self.view.headers['X-Throttle'] = header
|
||||
raise _503_SERVICE_UNAVAILABLE
|
||||
raise Throttled(wait)
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
|
@ -215,7 +209,7 @@ class BaseThrottle(BasePermission):
|
|||
|
||||
available_requests = self.num_requests - len(self.history) + 1
|
||||
|
||||
return '%.2f' % (remaining_duration / float(available_requests))
|
||||
return remaining_duration / float(available_requests)
|
||||
|
||||
|
||||
class PerUserThrottling(BaseThrottle):
|
||||
|
|
|
@ -161,25 +161,3 @@ class Response(SimpleTemplateResponse):
|
|||
},
|
||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
view=self.view, request=self.request, renderers=[renderer])
|
||||
|
||||
|
||||
class ImmediateResponse(Response, Exception):
|
||||
"""
|
||||
An exception representing an Response that should be returned immediately.
|
||||
Any content should be serialized as-is, without being filtered.
|
||||
"""
|
||||
#TODO: this is just a temporary fix, the whole rendering/support for ImmediateResponse, should be remade : see issue #163
|
||||
|
||||
def render(self):
|
||||
try:
|
||||
return super(Response, self).render()
|
||||
except ImmediateResponse:
|
||||
renderer, media_type = self._determine_renderer()
|
||||
self.renderers.remove(renderer)
|
||||
if len(self.renderers) == 0:
|
||||
raise RuntimeError('Caught an ImmediateResponse while '\
|
||||
'trying to render an ImmediateResponse')
|
||||
return self.render()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.response = Response(*args, **kwargs)
|
||||
|
|
|
@ -4,7 +4,7 @@ import unittest
|
|||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework.response import Response, NotAcceptable, ImmediateResponse
|
||||
from djangorestframework.response import Response, NotAcceptable
|
||||
from djangorestframework.views import View
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework import status
|
||||
|
|
|
@ -45,7 +45,7 @@ class ThrottlingTests(TestCase):
|
|||
request = self.factory.get('/')
|
||||
for dummy in range(4):
|
||||
response = MockView.as_view()(request)
|
||||
self.assertEqual(503, response.status_code)
|
||||
self.assertEqual(429, response.status_code)
|
||||
|
||||
def set_throttle_timer(self, view, value):
|
||||
"""
|
||||
|
@ -62,7 +62,7 @@ class ThrottlingTests(TestCase):
|
|||
request = self.factory.get('/')
|
||||
for dummy in range(4):
|
||||
response = MockView.as_view()(request)
|
||||
self.assertEqual(503, response.status_code)
|
||||
self.assertEqual(429, response.status_code)
|
||||
|
||||
# Advance the timer by one second
|
||||
self.set_throttle_timer(MockView, 1)
|
||||
|
@ -90,7 +90,7 @@ class ThrottlingTests(TestCase):
|
|||
"""
|
||||
Ensure request rate is limited globally per View for PerViewThrottles
|
||||
"""
|
||||
self.ensure_is_throttled(MockView_PerViewThrottling, 503)
|
||||
self.ensure_is_throttled(MockView_PerViewThrottling, 429)
|
||||
|
||||
def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers):
|
||||
"""
|
||||
|
|
|
@ -6,12 +6,14 @@ By setting or modifying class attributes on your view, you change it's predefine
|
|||
"""
|
||||
|
||||
import re
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from djangorestframework.compat import View as DjangoView, apply_markdown
|
||||
from djangorestframework.response import Response, ImmediateResponse
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework import renderers, parsers, authentication, permissions, status, exceptions
|
||||
|
||||
|
@ -219,13 +221,27 @@ class View(DjangoView):
|
|||
response[key] = value
|
||||
return response
|
||||
|
||||
def handle_exception(self, exc):
|
||||
"""
|
||||
Handle any exception that occurs, by returning an appropriate response,
|
||||
or re-raising the error.
|
||||
"""
|
||||
if isinstance(exc, exceptions.REST_FRAMEWORK_EXCEPTIONS):
|
||||
return Response({'detail': exc.detail}, status=exc.status_code)
|
||||
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)
|
||||
raise
|
||||
|
||||
# Note: session based authentication is explicitly CSRF validated,
|
||||
# all other authentication is CSRF exempt.
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
request = Request(request, parsers=self.parsers, authentication=self.authentication)
|
||||
self.request = request
|
||||
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.headers = self.default_response_headers
|
||||
|
@ -244,10 +260,8 @@ class View(DjangoView):
|
|||
|
||||
response = handler(request, *args, **kwargs)
|
||||
|
||||
except ImmediateResponse, exc:
|
||||
response = exc.response
|
||||
except (exceptions.ParseError, exceptions.PermissionDenied) as exc:
|
||||
response = Response({'detail': exc.detail}, status=exc.status_code)
|
||||
except Exception as exc:
|
||||
response = self.handle_exception(exc)
|
||||
|
||||
self.response = self.final(request, response, *args, **kwargs)
|
||||
return self.response
|
||||
|
@ -265,4 +279,4 @@ class View(DjangoView):
|
|||
for name, field in form.fields.iteritems():
|
||||
field_name_types[name] = field.__class__.__name__
|
||||
content['fields'] = field_name_types
|
||||
raise ImmediateResponse(content, status=status.HTTP_200_OK)
|
||||
raise Response(content, status=status.HTTP_200_OK)
|
||||
|
|
Loading…
Reference in New Issue
Block a user