diff options
| author | Tom Christie | 2011-04-25 01:03:23 +0100 | 
|---|---|---|
| committer | Tom Christie | 2011-04-25 01:03:23 +0100 | 
| commit | 4692374e0d6f020f8a7a95f3a60094d525c59341 (patch) | |
| tree | 016dec93ce950027e2ee6f4a6b8c0e1d5ecf2037 /djangorestframework/resource.py | |
| parent | cb4b4f6be6eeac3d2383614998a5e1436cb4226e (diff) | |
| download | django-rest-framework-4692374e0d6f020f8a7a95f3a60094d525c59341.tar.bz2 | |
Generic permissions added, allowed_methods and anon_allowed_methods now defunct, dispatch now mirrors View.dispatch more nicely
Diffstat (limited to 'djangorestframework/resource.py')
| -rw-r--r-- | djangorestframework/resource.py | 163 | 
1 files changed, 61 insertions, 102 deletions
| diff --git a/djangorestframework/resource.py b/djangorestframework/resource.py index 1e79c79f..55a9b57d 100644 --- a/djangorestframework/resource.py +++ b/djangorestframework/resource.py @@ -4,7 +4,7 @@ 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, validators, status +from djangorestframework import emitters, parsers, authenticators, permissions, validators, status  # TODO: Figure how out references and named urls need to work nicely @@ -19,11 +19,7 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):      """Handles incoming requests and maps them to REST operations,      performing authentication, input deserialization, input validation, output serialization.""" -    # List of RESTful operations which may be performed on this resource. -    # These are going to get dropped at some point, the allowable methods will be defined simply by -    # which methods are present on the request (in the same way as Django's generic View) -    allowed_methods = ('GET',) -    anon_allowed_methods = () +    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, @@ -37,12 +33,15 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):                  parsers.FormParser,                  parsers.MultipartParser ) -    # List of validators to validate, cleanup and type-ify the request content     +    # 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 = ( permissions.DeleteMePermission, )      # Optional form for input validation and presentation of HTML formatted responses.      form = None @@ -53,52 +52,14 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):      name = None      description = None -    # Map standard HTTP methods to function calls -    callmap = { 'GET': 'get', 'POST': 'post',  -                'PUT': 'put', 'DELETE': 'delete' } - -    def get(self, request, *args, **kwargs): -        """Must be subclassed to be implemented.""" -        self.not_implemented('GET') - - -    def post(self, request, *args, **kwargs): -        """Must be subclassed to be implemented.""" -        self.not_implemented('POST') - - -    def put(self, request, *args, **kwargs): -        """Must be subclassed to be implemented.""" -        self.not_implemented('PUT') - - -    def delete(self, request, *args, **kwargs): -        """Must be subclassed to be implemented.""" -        self.not_implemented('DELETE') +    @property +    def allowed_methods(self): +        return [method.upper() for method in self.http_method_names if hasattr(self, method)] - -    def not_implemented(self, operation): -        """Return an HTTP 500 server error if an operation is called which has been allowed by -        allowed_methods, but which has not been implemented.""" -        raise ErrorResponse(status.HTTP_500_INTERNAL_SERVER_ERROR, -                                {'detail': '%s operation on this resource has not been implemented' % (operation, )}) - - -    def check_method_allowed(self, method, auth): -        """Ensure the request method is permitted for this resource, raising a ResourceException if it is not.""" - -        if not method in self.callmap.keys(): -            raise ErrorResponse(status.HTTP_501_NOT_IMPLEMENTED, -                                    {'detail': 'Unknown or unsupported method \'%s\'' % method}) - -        if not method in self.allowed_methods: -            raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED, -                                    {'detail': 'Method \'%s\' not allowed on this resource.' % method}) - -        if auth is None and not method in self.anon_allowed_methods: -            raise ErrorResponse(status.HTTP_403_FORBIDDEN, -                                    {'detail': 'You do not have permission to access this resource. ' + -                                     'You may need to login or otherwise authenticate the request.'}) +    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): @@ -111,6 +72,7 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):          the EmitterMixin and Emitter classes."""          return data +      # Session based authentication is explicitly CSRF validated, all other authentication is CSRF exempt.      @csrf_exempt      def dispatch(self, request, *args, **kwargs): @@ -125,57 +87,54 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):          4. cleanup the response data          5. serialize response data into response content, using standard HTTP content negotiation          """ - -        self.request = request - -        # 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: -            # Authenticate the request, and store any context so that the resource operations can -            # do more fine grained authentication if required. +            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) +                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.              # -            # Typically the context will be a user, or None if this is an anonymous request, -            # but it could potentially be more complex (eg the context of a request key which -            # has been signed against a particular set of permissions) -            auth_context = self.auth - -            # If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter -            # self.method, self.content_type, self.CONTENT appropriately. -            self.perform_form_overloading() - -            # Ensure the requested operation is permitted on this resource -            self.check_method_allowed(self.method, auth_context) - -            # Get the appropriate create/read/update/delete function -            func = getattr(self, self.callmap.get(self.method, None)) +            # 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' -            # Either generate the response data, deserializing and validating any request data -            # TODO: This is going to change to: func(request, *args, **kwargs) -            # That'll work out now that we have the lazily evaluated self.CONTENT property. -            response_obj = func(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) +            return self.emit(response) +        except: +            import traceback +            traceback.print_exc() | 
