diff options
| author | Tom Christie | 2012-09-20 13:06:27 +0100 | 
|---|---|---|
| committer | Tom Christie | 2012-09-20 13:06:27 +0100 | 
| commit | 4b691c402707775c3048a90531024f3bc5be6f91 (patch) | |
| tree | 3adfc54b0d8b70e4ea78edf7091f7827fa68f47b /rest_framework/views.py | |
| parent | a1bcfbfe926621820832e32b0427601e1140b4f7 (diff) | |
| download | django-rest-framework-4b691c402707775c3048a90531024f3bc5be6f91.tar.bz2 | |
Change package name: djangorestframework -> rest_framework
Diffstat (limited to 'rest_framework/views.py')
| -rw-r--r-- | rest_framework/views.py | 304 | 
1 files changed, 304 insertions, 0 deletions
diff --git a/rest_framework/views.py b/rest_framework/views.py new file mode 100644 index 00000000..62be157d --- /dev/null +++ b/rest_framework/views.py @@ -0,0 +1,304 @@ +""" +The :mod:`views` module provides the Views you will most probably +be subclassing in your implementation. + +By setting or modifying class attributes on your view, you change it's predefined behaviour. +""" + +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 rest_framework.compat import View as _View, apply_markdown +from rest_framework.response import Response +from rest_framework.request import Request +from rest_framework.settings import api_settings +from rest_framework import status, exceptions + + +def _remove_trailing_string(content, trailing): +    """ +    Strip trailing component `trailing` from `content` if it exists. +    Used when generating names from view classes. +    """ +    if content.endswith(trailing) and content != trailing: +        return content[:-len(trailing)] +    return content + + +def _remove_leading_indent(content): +    """ +    Remove leading indent from a block of text. +    Used when generating descriptions from docstrings. +    """ +    whitespace_counts = [len(line) - len(line.lstrip(' ')) +                         for line in content.splitlines()[1:] if line.lstrip()] + +    # unindent the content if needed +    if whitespace_counts: +        whitespace_pattern = '^' + (' ' * min(whitespace_counts)) +        return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) +    return content + + +def _camelcase_to_spaces(content): +    """ +    Translate 'CamelCaseNames' to 'Camel Case Names'. +    Used when generating names from view classes. +    """ +    camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))' +    return re.sub(camelcase_boundry, ' \\1', content).strip() + + +class APIView(_View): +    settings = api_settings + +    renderer_classes = api_settings.DEFAULT_RENDERERS +    parser_classes = api_settings.DEFAULT_PARSERS +    authentication_classes = api_settings.DEFAULT_AUTHENTICATION +    throttle_classes = api_settings.DEFAULT_THROTTLES +    permission_classes = api_settings.DEFAULT_PERMISSIONS +    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION + +    @classmethod +    def as_view(cls, **initkwargs): +        """ +        Override the default :meth:`as_view` to store an instance of the view +        as an attribute on the callable function.  This allows us to discover +        information about the view when we do URL reverse lookups. +        """ +        view = super(APIView, cls).as_view(**initkwargs) +        view.cls_instance = cls(**initkwargs) +        return view + +    @property +    def allowed_methods(self): +        """ +        Return the list of allowed HTTP methods, uppercased. +        """ +        return [method.upper() for method in self.http_method_names +                if hasattr(self, method)] + +    @property +    def default_response_headers(self): +        return { +            'Allow': ', '.join(self.allowed_methods), +            'Vary': 'Authenticate, Accept' +        } + +    def get_name(self): +        """ +        Return the resource or view class name for use as this view's name. +        Override to customize. +        """ +        name = self.__class__.__name__ +        name = _remove_trailing_string(name, 'View') +        return _camelcase_to_spaces(name) + +    def get_description(self, html=False): +        """ +        Return the resource or view docstring for use as this view's description. +        Override to customize. +        """ +        description = self.__doc__ or '' +        description = _remove_leading_indent(description) +        if html: +            return self.markup_description(description) +        return description + +    def markup_description(self, description): +        """ +        Apply HTML markup to the description of this view. +        """ +        if apply_markdown: +            description = apply_markdown(description) +        else: +            description = escape(description).replace('\n', '<br />') +        return mark_safe(description) + +    def http_method_not_allowed(self, request, *args, **kwargs): +        """ +        Called if `request.method` does not corrospond to a handler method. +        """ +        raise exceptions.MethodNotAllowed(request.method) + +    def permission_denied(self, request): +        """ +        If request is not permitted, determine what kind of exception to raise. +        """ +        raise exceptions.PermissionDenied() + +    def throttled(self, request, wait): +        """ +        If request is throttled, determine what kind of exception to raise. +        """ +        raise exceptions.Throttled(wait) + +    @property +    def _parsed_media_types(self): +        """ +        Return a list of all the media types that this view can parse. +        """ +        return [parser.media_type for parser in self.parser_classes] + +    @property +    def _default_parser(self): +        """ +        Return the view's default parser class. +        """ +        return self.parser_classes[0] + +    @property +    def _rendered_media_types(self): +        """ +        Return an list of all the media types that this response can render. +        """ +        return [renderer.media_type for renderer in self.renderer_classes] + +    @property +    def _rendered_formats(self): +        """ +        Return a list of all the formats that this response can render. +        """ +        return [renderer.format for renderer in self.renderer_classes] + +    @property +    def _default_renderer(self): +        """ +        Return the response's default renderer class. +        """ +        return self.renderer_classes[0] + +    def get_format_suffix(self, **kwargs): +        """ +        Determine if the request includes a '.json' style format suffix +        """ +        if self.settings.FORMAT_SUFFIX_KWARG: +            return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG) + +    def get_renderers(self, format=None): +        """ +        Instantiates and returns the list of renderers that this view can use. +        """ +        return [renderer(self) for renderer in self.renderer_classes] + +    def get_permissions(self): +        """ +        Instantiates and returns the list of permissions that this view requires. +        """ +        return [permission(self) for permission in self.permission_classes] + +    def get_throttles(self): +        """ +        Instantiates and returns the list of thottles that this view uses. +        """ +        return [throttle(self) for throttle in self.throttle_classes] + +    def content_negotiation(self, request, force=False): +        """ +        Determine which renderer and media type to use render the response. +        """ +        renderers = self.get_renderers() +        conneg = self.content_negotiation_class() +        return conneg.negotiate(request, renderers, self.format, force) + +    def check_permissions(self, request, obj=None): +        """ +        Check if request should be permitted. +        """ +        for permission in self.get_permissions(): +            if not permission.has_permission(request, obj): +                self.permission_denied(request) + +    def check_throttles(self, request): +        """ +        Check if request should be throttled. +        """ +        for throttle in self.get_throttles(): +            if not throttle.allow_request(request): +                self.throttled(request, throttle.wait()) + +    def initialize_request(self, request, *args, **kargs): +        """ +        Returns the initial request object. +        """ +        return Request(request, parser_classes=self.parser_classes, +                       authentication_classes=self.authentication_classes) + +    def initial(self, request, *args, **kwargs): +        """ +        Runs anything that needs to occur prior to calling the method handlers. +        """ +        self.format = self.get_format_suffix(**kwargs) +        self.check_permissions(request) +        self.check_throttles(request) +        self.renderer, self.media_type = self.content_negotiation(request) + +    def finalize_response(self, request, response, *args, **kwargs): +        """ +        Returns the final response object. +        """ +        if isinstance(response, Response): +            if not getattr(self, 'renderer', None): +                self.renderer, self.media_type = self.content_negotiation(request, force=True) +            response.renderer = self.renderer +            response.media_type = self.media_type + +        for key, value in self.headers.items(): +            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.Throttled): +            # Throttle wait header +            self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait + +        if isinstance(exc, exceptions.APIException): +            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): +        """ +        `.dispatch()` is pretty much the same as Django's regular dispatch, +        but with extra hooks for startup, finalize, and exception handling. +        """ +        request = self.initialize_request(request, *args, **kwargs) +        self.request = request +        self.args = args +        self.kwargs = kwargs +        self.headers = self.default_response_headers + +        try: +            self.initial(request, *args, **kwargs) + +            # Get the appropriate handler method +            if request.method.lower() in self.http_method_names: +                handler = getattr(self, request.method.lower(), +                                  self.http_method_not_allowed) +            else: +                handler = self.http_method_not_allowed + +            response = handler(request, *args, **kwargs) + +        except Exception as exc: +            response = self.handle_exception(exc) + +        self.response = self.finalize_response(request, response, *args, **kwargs) +        return self.response  | 
