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() |
