aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/resource.py
blob: fdbce8b5be6ae3ed938935e829083de9946e74de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from django.core.urlresolvers import set_script_prefix
from django.views.decorators.csrf import csrf_exempt

from djangorestframework.compat import View
from djangorestframework.response import Response, ErrorResponse
from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin
from djangorestframework import emitters, parsers, authenticators, permissions, validators, status


# TODO: Figure how out references and named urls need to work nicely
# TODO: POST on existing 404 URL, PUT on existing 404 URL
#
# NEXT: Exceptions on func() -> 500, tracebacks emitted if settings.DEBUG

__all__ = ['Resource']


class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
    """Handles incoming requests and maps them to REST operations.
    Performs request deserialization, response serialization, authentication and input validation."""

    http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace', 'patch']

    # List of emitters the resource can serialize the response with, ordered by preference.
    emitters = ( emitters.JSONEmitter,
                 emitters.DocumentingHTMLEmitter,
                 emitters.DocumentingXHTMLEmitter,
                 emitters.DocumentingPlainTextEmitter,
                 emitters.XMLEmitter )

    # List of parsers the resource can parse the request with.
    parsers = ( parsers.JSONParser,
                parsers.FormParser,
                parsers.MultipartParser )

    # List of validators to validate, cleanup and normalize the request content    
    validators = ( validators.FormValidator, )

    # List of all authenticating methods to attempt.
    authenticators = ( authenticators.UserLoggedInAuthenticator,
                       authenticators.BasicAuthenticator )
    
    # List of all permissions required to access the resource
    permissions = ()

    # Optional form for input validation and presentation of HTML formatted responses.
    form = None

    # Allow name and description for the Resource to be set explicitly,
    # overiding the default classname/docstring behaviour.
    # These are used for documentation in the standard html and text emitters.
    name = None
    description = None

    @property
    def allowed_methods(self):
        return [method.upper() for method in self.http_method_names if getattr(self, method, None)]

    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."""
        raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
                                {'detail': 'Method \'%s\' not allowed on this resource.' % self.method})


    def cleanup_response(self, data):
        """Perform any resource-specific data filtering prior to the standard HTTP
        content-type serialization.

        Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can.
        
        TODO: This is going to be removed.  I think that the 'fields' behaviour is going to move into
        the EmitterMixin and Emitter classes."""
        return data


    # Note: session based authentication is explicitly CSRF validated,
    # all other authentication is CSRF exempt.
    @csrf_exempt
    def dispatch(self, request, *args, **kwargs):
        try:
            self.request = request
            self.args = args
            self.kwargs = kwargs
    
            # Calls to 'reverse' will not be fully qualified unless we set the scheme/host/port here.
            prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host())
            set_script_prefix(prefix)
    
            try:
                # If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter
                # self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately.
                self.perform_form_overloading()
    
                # Authenticate and check request is has the relevant permissions
                self.check_permissions()
    
                # Get the appropriate handler method
                if self.method.lower() in self.http_method_names:
                    handler = getattr(self, self.method.lower(), self.http_method_not_allowed)
                    # If a previously defined method has been disabled
                    if handler is None:
                        handler = self.http_method_not_allowed
                else:
                    handler = self.http_method_not_allowed
    
                response_obj = handler(request, *args, **kwargs)
    
                # Allow return value to be either Response, or an object, or None
                if isinstance(response_obj, Response):
                    response = response_obj
                elif response_obj is not None:
                    response = Response(status.HTTP_200_OK, response_obj)
                else:
                    response = Response(status.HTTP_204_NO_CONTENT)
    
                # Pre-serialize filtering (eg filter complex objects into natively serializable types)
                response.cleaned_content = self.cleanup_response(response.raw_content)
    
            except ErrorResponse, exc:
                response = exc.response
    
            # Always add these headers.
            #
            # TODO - this isn't actually the correct way to set the vary header,
            # also it's currently sub-obtimal for HTTP caching - need to sort that out. 
            response.headers['Allow'] = ', '.join(self.allowed_methods)
            response.headers['Vary'] = 'Authenticate, Accept'
    
            return self.emit(response)
        except:
            import traceback
            traceback.print_exc()