aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/resource.py
diff options
context:
space:
mode:
authortom christie tom@tomchristie.com2011-02-07 08:23:54 +0000
committertom christie tom@tomchristie.com2011-02-07 08:23:54 +0000
commit027ffed21064b1ec304a1ea559104382313d76f4 (patch)
tree8621c8741d76cc672da207cf314574a4fbd828d1 /djangorestframework/resource.py
parenta8bcb2edc63564522b89b9950ea0882d6b25f24a (diff)
downloaddjango-rest-framework-027ffed21064b1ec304a1ea559104382313d76f4.tar.bz2
Refactor a bunch of stuff into mixins, more tests
Diffstat (limited to 'djangorestframework/resource.py')
-rw-r--r--djangorestframework/resource.py126
1 files changed, 10 insertions, 116 deletions
diff --git a/djangorestframework/resource.py b/djangorestframework/resource.py
index 6277d22e..c743ce8f 100644
--- a/djangorestframework/resource.py
+++ b/djangorestframework/resource.py
@@ -2,6 +2,10 @@ from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.http import HttpResponse
+from djangorestframework.parsers import ParserMixin
+from djangorestframework.validators import FormValidatorMixin
+from djangorestframework.content import OverloadedContentMixin
+from djangorestframework.methods import OverloadedPOSTMethodMixin
from djangorestframework import emitters, parsers, authenticators
from djangorestframework.response import status, Response, ResponseException
@@ -20,7 +24,7 @@ __all__ = ['Resource']
_MSIE_USER_AGENT = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
-class Resource(object):
+class Resource(ParserMixin, FormValidatorMixin, OverloadedContentMixin, OverloadedPOSTMethodMixin):
"""Handles incoming requests and maps them to REST operations,
performing authentication, input deserialization, input validation, output serialization."""
@@ -53,10 +57,7 @@ class Resource(object):
# Some reserved parameters to allow us to use standard HTML forms with our resource
# Override any/all of these with None to disable them, or override them with another value to rename them.
- ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
- METHOD_PARAM = '_method' # Allow POST overloading in form params
- CONTENTTYPE_PARAM = '_contenttype' # Allow override of Content-Type header in form params (allows sending arbitrary content with standard forms)
- CONTENT_PARAM = '_content' # Allow override of body content in form params (allows sending arbitrary content with standard forms)
+ ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params CONTENTTYPE_PARAM = '_contenttype' # Allow override of Content-Type header in form params (allows sending arbitrary content with standard forms)
CSRF_PARAM = 'csrfmiddlewaretoken' # Django's CSRF token used in form params
_MUNGE_IE_ACCEPT_HEADER = True
@@ -112,18 +113,6 @@ class Resource(object):
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
return self.emitters[0]
- @property
- def parsed_media_types(self):
- """Return an list of all the media types that this resource can emit."""
- return [parser.media_type for parser in self.parsers]
-
- @property
- def default_parser(self):
- """Return the resource's most prefered emitter.
- (This has no behavioural effect, but is may be used by documenting emitters)"""
- return self.parsers[0]
-
-
def get(self, request, auth, *args, **kwargs):
"""Must be subclassed to be implemented."""
self.not_implemented('GET')
@@ -171,17 +160,6 @@ class Resource(object):
pass
return self.request.build_absolute_uri(path)
-
-
- def determine_method(self, request):
- """Determine the HTTP method that this request should be treated as.
- Allows PUT and DELETE tunneling via the _method parameter if METHOD_PARAM is set."""
- method = request.method.upper()
-
- if method == 'POST' and self.METHOD_PARAM and request.POST.has_key(self.METHOD_PARAM):
- method = request.POST[self.METHOD_PARAM].upper()
-
- return method
def authenticate(self, request):
@@ -214,58 +192,6 @@ class Resource(object):
{'detail': 'You do not have permission to access this resource. ' +
'You may need to login or otherwise authenticate the request.'})
- def get_form(self, data=None):
- """Optionally return a Django Form instance, which may be used for validation
- and/or rendered by an HTML/XHTML emitter.
-
- If data is not None the form will be bound to data."""
-
- if self.form:
- if data:
- return self.form(data)
- else:
- return self.form()
- return None
-
-
- def cleanup_request(self, data, form_instance):
- """Perform any resource-specific data deserialization and/or validation
- after the initial HTTP content-type deserialization has taken place.
-
- Returns a tuple containing the cleaned up data, and optionally a form bound to that data.
-
- By default this uses form validation to filter the basic input into the required types."""
-
- if form_instance is None:
- return data
-
- # Default form validation does not check for additional invalid fields
- non_existent_fields = []
- for key in set(data.keys()) - set(form_instance.fields.keys()):
- non_existent_fields.append(key)
-
- if not form_instance.is_valid() or non_existent_fields:
- if not form_instance.errors and not non_existent_fields:
- # If no data was supplied the errors property will be None
- details = 'No content was supplied'
-
- else:
- # Add standard field errors
- details = dict((key, map(unicode, val)) for (key, val) in form_instance.errors.iteritems() if key != '__all__')
-
- # Add any non-field errors
- if form_instance.non_field_errors():
- details['errors'] = form_instance.non_field_errors()
-
- # Add any non-existent field errors
- for key in non_existent_fields:
- details[key] = ['This field does not exist']
-
- # Bail. Note that we will still serialize this response with the appropriate content type
- raise ResponseException(status.HTTP_400_BAD_REQUEST, {'detail': details})
-
- return form_instance.cleaned_data
-
def cleanup_response(self, data):
"""Perform any resource-specific data filtering prior to the standard HTTP
@@ -275,37 +201,6 @@ class Resource(object):
return data
- def determine_parser(self, request):
- """Return the appropriate parser for the input, given the client's 'Content-Type' header,
- and the content types that this Resource knows how to parse."""
- content_type = request.META.get('CONTENT_TYPE', 'application/x-www-form-urlencoded')
- raw_content = request.raw_post_data
-
- split = content_type.split(';', 1)
- if len(split) > 1:
- content_type = split[0]
- content_type = content_type.strip()
-
- # If CONTENTTYPE_PARAM is turned on, and this is a standard POST form then allow the content type to be overridden
- if (content_type == 'application/x-www-form-urlencoded' and
- request.method == 'POST' and
- self.CONTENTTYPE_PARAM and
- self.CONTENT_PARAM and
- request.POST.get(self.CONTENTTYPE_PARAM, None) and
- request.POST.get(self.CONTENT_PARAM, None)):
- raw_content = request.POST[self.CONTENT_PARAM]
- content_type = request.POST[self.CONTENTTYPE_PARAM]
-
- # Create a list of list of (media_type, Parser) tuples
- media_type_to_parser = dict([(parser.media_type, parser) for parser in self.parsers])
-
- try:
- return (media_type_to_parser[content_type], raw_content)
- except KeyError:
- raise ResponseException(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
- {'detail': 'Unsupported media type \'%s\'' % content_type})
-
-
def determine_emitter(self, request):
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
and the content types that this Resource knows how to serve.
@@ -407,11 +302,10 @@ class Resource(object):
# Either generate the response data, deserializing and validating any request data
# TODO: Add support for message bodys on other HTTP methods, as it is valid.
if method in ('PUT', 'POST'):
- (parser, raw_content) = self.determine_parser(request)
- data = parser(self).parse(raw_content)
- self.form_instance = self.get_form(data)
- data = self.cleanup_request(data, self.form_instance)
- response = func(request, auth_context, data, *args, **kwargs)
+ (content_type, content) = self.determine_content(request)
+ parser_content = self.parse(content_type, content)
+ cleaned_content = self.validate(parser_content)
+ response = func(request, auth_context, cleaned_content, *args, **kwargs)
else:
response = func(request, auth_context, *args, **kwargs)