aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/resource.py
diff options
context:
space:
mode:
Diffstat (limited to 'djangorestframework/resource.py')
-rw-r--r--djangorestframework/resource.py186
1 files changed, 0 insertions, 186 deletions
diff --git a/djangorestframework/resource.py b/djangorestframework/resource.py
deleted file mode 100644
index 80e5df2a..00000000
--- a/djangorestframework/resource.py
+++ /dev/null
@@ -1,186 +0,0 @@
-from django.core.urlresolvers import set_script_prefix
-from django.views.decorators.csrf import csrf_exempt
-
-from djangorestframework.compat import View
-from djangorestframework.emitters import EmitterMixin
-from djangorestframework.parsers import ParserMixin
-from djangorestframework.authenticators import AuthenticatorMixin
-from djangorestframework.validators import FormValidatorMixin
-from djangorestframework.response import Response, ResponseException
-from djangorestframework.request import RequestMixin
-from djangorestframework import emitters, parsers, authenticators, 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(EmitterMixin, ParserMixin, AuthenticatorMixin, FormValidatorMixin, RequestMixin, 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 = ()
-
- # 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 all authenticating methods to attempt.
- authenticators = ( authenticators.UserLoggedInAuthenticator,
- authenticators.BasicAuthenticator )
-
- # 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
-
- # Map standard HTTP methods to function calls
- callmap = { 'GET': 'get', 'POST': 'post',
- 'PUT': 'put', 'DELETE': 'delete' }
-
- def get(self, request, auth, *args, **kwargs):
- """Must be subclassed to be implemented."""
- self.not_implemented('GET')
-
-
- def post(self, request, auth, content, *args, **kwargs):
- """Must be subclassed to be implemented."""
- self.not_implemented('POST')
-
-
- def put(self, request, auth, content, *args, **kwargs):
- """Must be subclassed to be implemented."""
- self.not_implemented('PUT')
-
-
- def delete(self, request, auth, *args, **kwargs):
- """Must be subclassed to be implemented."""
- self.not_implemented('DELETE')
-
-
- 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 ResponseException(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 ResponseException(status.HTTP_501_NOT_IMPLEMENTED,
- {'detail': 'Unknown or unsupported method \'%s\'' % method})
-
- if not method in self.allowed_methods:
- raise ResponseException(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 ResponseException(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 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
-
- # Session based authentication is explicitly CSRF validated, all other authentication is CSRF exempt.
- @csrf_exempt
- def dispatch(self, request, *args, **kwargs):
- """This method is the core of Resource, through which all requests are passed.
-
- Broadly this consists of the following procedure:
-
- 0. ensure the operation is permitted
- 1. deserialize request content into request data, using standard HTTP content types (PUT/POST only)
- 2. cleanup and validate request data (PUT/POST only)
- 3. call the core method to get the response data
- 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.
- #
- # 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.authenticate(request)
-
- # 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))
-
- # 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.
- if self.method in ('PUT', 'POST'):
- response_obj = func(request, auth_context, self.CONTENT, *args, **kwargs)
-
- else:
- response_obj = func(request, auth_context, *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 ResponseException, 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)
-