aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework
diff options
context:
space:
mode:
authorTom Christie2012-09-03 15:57:43 +0100
committerTom Christie2012-09-03 15:57:43 +0100
commit149b00a070fcbfd44feee5b37096081e18356f93 (patch)
tree4ce2586f6a15613625a23d80b4624e64a9b94de6 /djangorestframework
parent7abef9ac3b3fb20a6cdef5d52c640e5725c93437 (diff)
downloaddjango-rest-framework-149b00a070fcbfd44feee5b37096081e18356f93.tar.bz2
Added the api_view decorator
Diffstat (limited to 'djangorestframework')
-rw-r--r--djangorestframework/decorators.py43
-rw-r--r--djangorestframework/parsers.py31
-rw-r--r--djangorestframework/response.py8
-rw-r--r--djangorestframework/settings.py49
-rw-r--r--djangorestframework/views.py4
5 files changed, 123 insertions, 12 deletions
diff --git a/djangorestframework/decorators.py b/djangorestframework/decorators.py
new file mode 100644
index 00000000..ff158367
--- /dev/null
+++ b/djangorestframework/decorators.py
@@ -0,0 +1,43 @@
+from functools import wraps
+from django.http import Http404
+from django.utils.decorators import available_attrs
+from django.core.exceptions import PermissionDenied
+from djangorestframework import exceptions
+from djangorestframework import status
+from djangorestframework.response import Response
+from djangorestframework.request import Request
+
+
+def api_view(allowed_methods):
+ """
+ Decorator to make a view only accept particular request methods. Usage::
+
+ @api_view(['GET', 'POST'])
+ def my_view(request):
+ # request will be an instance of `Request`
+ # APIException instances will be handled
+
+ Note that request methods should be in uppercase.
+ """
+ allowed_methods = [method.upper() for method in allowed_methods]
+
+ def decorator(func):
+ @wraps(func, assigned=available_attrs(func))
+ def inner(request, *args, **kwargs):
+ try:
+ request = Request(request)
+ if request.method not in allowed_methods:
+ return exceptions.MethodNotAllowed(request.method)
+ response = func(request, *args, **kwargs)
+ response.request = request
+ return response
+ except exceptions.APIException as exc:
+ return Response({'detail': exc.detail}, status=exc.status_code)
+ except Http404 as exc:
+ return Response({'detail': 'Not found'},
+ status=status.HTTP_404_NOT_FOUND)
+ except PermissionDenied as exc:
+ return Response({'detail': 'Permission denied'},
+ status=status.HTTP_403_FORBIDDEN)
+ return inner
+ return decorator
diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py
index fc0260fc..96dd81ed 100644
--- a/djangorestframework/parsers.py
+++ b/djangorestframework/parsers.py
@@ -23,6 +23,7 @@ from djangorestframework.compat import ETParseError
from xml.parsers.expat import ExpatError
import datetime
import decimal
+from io import BytesIO
__all__ = (
@@ -63,12 +64,24 @@ class BaseParser(object):
"""
return media_type_matches(self.media_type, content_type)
- def parse(self, stream, **opts):
+ def parse(self, string_or_stream, **opts):
+ """
+ The main entry point to parsers. This is a light wrapper around
+ `parse_stream`, that instead handles both string and stream objects.
+ """
+ if isinstance(string_or_stream, basestring):
+ stream = BytesIO(string_or_stream)
+ else:
+ stream = string_or_stream
+ return self.parse_stream(stream, **opts)
+
+ def parse_stream(self, stream, **opts):
"""
Given a *stream* to read from, return the deserialized output.
- Should return a 2-tuple of (data, files).
+ Should return parsed data, or a DataAndFiles object consisting of the
+ parsed data and files.
"""
- raise NotImplementedError(".parse() Must be overridden to be implemented.")
+ raise NotImplementedError(".parse_stream() Must be overridden to be implemented.")
class JSONParser(BaseParser):
@@ -78,7 +91,7 @@ class JSONParser(BaseParser):
media_type = 'application/json'
- def parse(self, stream, **opts):
+ def parse_stream(self, stream, **opts):
"""
Returns a 2-tuple of `(data, files)`.
@@ -98,7 +111,7 @@ class YAMLParser(BaseParser):
media_type = 'application/yaml'
- def parse(self, stream, **opts):
+ def parse_stream(self, stream, **opts):
"""
Returns a 2-tuple of `(data, files)`.
@@ -118,7 +131,7 @@ class PlainTextParser(BaseParser):
media_type = 'text/plain'
- def parse(self, stream, **opts):
+ def parse_stream(self, stream, **opts):
"""
Returns a 2-tuple of `(data, files)`.
@@ -135,7 +148,7 @@ class FormParser(BaseParser):
media_type = 'application/x-www-form-urlencoded'
- def parse(self, stream, **opts):
+ def parse_stream(self, stream, **opts):
"""
Returns a 2-tuple of `(data, files)`.
@@ -153,7 +166,7 @@ class MultiPartParser(BaseParser):
media_type = 'multipart/form-data'
- def parse(self, stream, **opts):
+ def parse_stream(self, stream, **opts):
"""
Returns a DataAndFiles object.
@@ -177,7 +190,7 @@ class XMLParser(BaseParser):
media_type = 'application/xml'
- def parse(self, stream, **opts):
+ def parse_stream(self, stream, **opts):
try:
tree = ET.parse(stream)
except (ExpatError, ETParseError, ValueError), exc:
diff --git a/djangorestframework/response.py b/djangorestframework/response.py
index 9dde1f01..4664e079 100644
--- a/djangorestframework/response.py
+++ b/djangorestframework/response.py
@@ -14,6 +14,7 @@ from Internet Explorer user agents and use a sensible browser `Accept` header in
from django.template.response import SimpleTemplateResponse
from django.core.handlers.wsgi import STATUS_CODE_TEXT
+from djangorestframework.settings import api_settings
from djangorestframework.utils.mediatypes import order_by_precedence
from djangorestframework.utils import MSIE_USER_AGENT_REGEX
from djangorestframework import status
@@ -53,7 +54,12 @@ class Response(SimpleTemplateResponse):
"""
Instantiates and returns the list of renderers the response will use.
"""
- return [renderer(self.view) for renderer in self.renderers]
+ if self.renderers is None:
+ renderer_classes = api_settings.DEFAULT_RENDERERS
+ else:
+ renderer_classes = self.renderers
+
+ return [cls(self.view) for cls in renderer_classes]
@property
def rendered_content(self):
diff --git a/djangorestframework/settings.py b/djangorestframework/settings.py
new file mode 100644
index 00000000..1acaad0c
--- /dev/null
+++ b/djangorestframework/settings.py
@@ -0,0 +1,49 @@
+"""
+Settings for REST framework are all namespaced in the API_SETTINGS setting.
+For example your project's `settings.py` file might look like this:
+
+API_SETTINGS = {
+ 'DEFAULT_RENDERERS': (
+ 'djangorestframework.renderers.JSONRenderer',
+ 'djangorestframework.renderers.YAMLRenderer',
+ )
+ 'DEFAULT_PARSERS': (
+ 'djangorestframework.parsers.JSONParser',
+ 'djangorestframework.parsers.YAMLParser',
+ )
+}
+
+"""
+from django.conf import settings
+from djangorestframework import renderers
+from djangorestframework.compat import yaml
+
+
+DEFAULTS = {
+ 'DEFAULT_RENDERERS': (
+ renderers.JSONRenderer,
+ renderers.JSONPRenderer,
+ renderers.DocumentingHTMLRenderer,
+ renderers.DocumentingXHTMLRenderer,
+ renderers.DocumentingPlainTextRenderer,
+ renderers.XMLRenderer
+ )
+}
+
+if yaml:
+ DEFAULTS['DEFAULT_RENDERERS'] += (renderers.YAMLRenderer, )
+
+
+class APISettings(object):
+ def __getattr__(self, attr):
+ try:
+ return settings.API_SETTINGS[attr]
+ except (AttributeError, KeyError):
+ # 'API_SETTINGS' does not exist,
+ # or requested setting is not present in 'API_SETTINGS'.
+ try:
+ return DEFAULTS[attr]
+ except KeyError:
+ raise AttributeError("No such setting '%s'" % attr)
+
+api_settings = APISettings()
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index fa34dc9a..3b76988c 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -146,8 +146,8 @@ class View(DjangoView):
def http_method_not_allowed(self, request, *args, **kwargs):
"""
- Return an HTTP 405 error if an operation is called which does not have
- a handler method.
+ Called if `request.method` does not corrospond to a handler method.
+ We raise an exception, which is handled by `.handle_exception()`.
"""
raise exceptions.MethodNotAllowed(request.method)