diff options
| author | Tom Christie | 2012-09-03 15:57:43 +0100 |
|---|---|---|
| committer | Tom Christie | 2012-09-03 15:57:43 +0100 |
| commit | 149b00a070fcbfd44feee5b37096081e18356f93 (patch) | |
| tree | 4ce2586f6a15613625a23d80b4624e64a9b94de6 /djangorestframework | |
| parent | 7abef9ac3b3fb20a6cdef5d52c640e5725c93437 (diff) | |
| download | django-rest-framework-149b00a070fcbfd44feee5b37096081e18356f93.tar.bz2 | |
Added the api_view decorator
Diffstat (limited to 'djangorestframework')
| -rw-r--r-- | djangorestframework/decorators.py | 43 | ||||
| -rw-r--r-- | djangorestframework/parsers.py | 31 | ||||
| -rw-r--r-- | djangorestframework/response.py | 8 | ||||
| -rw-r--r-- | djangorestframework/settings.py | 49 | ||||
| -rw-r--r-- | djangorestframework/views.py | 4 |
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) |
