From 8f6bcac7f3f156831343cc7fec79f624dcc2639f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 13 May 2011 09:59:36 +0100 Subject: [PATCH] cleanup --- djangorestframework/mixins.py | 51 +++++++++- djangorestframework/parsers.py | 6 +- djangorestframework/renderers.py | 3 +- .../{resource.py => resources.py} | 20 +++- .../tests/{resource.py => resources.py} | 2 +- djangorestframework/views.py | 93 +++++++++---------- 6 files changed, 115 insertions(+), 60 deletions(-) rename djangorestframework/{resource.py => resources.py} (97%) rename djangorestframework/tests/{resource.py => resources.py} (94%) diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 524e92682..12f2d779a 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -46,12 +46,20 @@ class RequestMixin(object): _CONTENTTYPE_PARAM = '_content_type' _CONTENT_PARAM = '_content' + """ + The set of request parsers that the view can handle. + + Should be a tuple/list of classes as described in the ``parsers`` module. + """ parsers = () @property def method(self): """ Returns the HTTP method. + + This should be used instead of ``request.method``, as it allows the method + to be overridden by using a hidden form field on a form POST request. """ if not hasattr(self, '_method'): self._load_method_and_content_type() @@ -62,6 +70,10 @@ class RequestMixin(object): def content_type(self): """ Returns the content type header. + + This should be used instead of ``request.META.get('HTTP_CONTENT_TYPE')``, + as it allows the content type to be overridden by using a hidden form + field on a form POST request. """ if not hasattr(self, '_content_type'): self._load_method_and_content_type() @@ -71,7 +83,10 @@ class RequestMixin(object): @property def DATA(self): """ - Returns the request data. + Parses the request body and returns the data. + + Similar to ``request.POST``, except that it handles arbitrary parsers, + and also works on methods other than POST (eg PUT). """ if not hasattr(self, '_data'): self._load_data_and_files() @@ -81,7 +96,9 @@ class RequestMixin(object): @property def FILES(self): """ - Returns the request files. + Parses the request body and returns the files. + Similar to request.FILES, except that it handles arbitrary parsers, + and also works on methods other than POST (eg PUT). """ if not hasattr(self, '_files'): self._load_data_and_files() @@ -205,8 +222,14 @@ class ResponseMixin(object): _ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params _IGNORE_IE_ACCEPT_HEADER = True + """ + The set of response renderers that the view can handle. + + Should be a tuple/list of classes as described in the ``renderers`` module. + """ renderers = () - + + # TODO: wrap this behavior around dispatch(), ensuring it works # out of the box with existing Django classes that use render_to_response. def render(self, response): @@ -330,14 +353,33 @@ class AuthMixin(object): """ Simple mixin class to add authentication and permission checking to a ``View`` class. """ + + """ + The set of authentication types that this view can handle. + + + Should be a tuple/list of classes as described in the ``authentication`` module. + """ authentication = () + + """ + The set of permissions that will be enforced on this view. + + Should be a tuple/list of classes as described in the ``permissions`` module. + """ permissions = () + @property def user(self): + """ + Returns the user for the current request, as determined by the set of + authentication classes applied to the ``View``. + """ if not hasattr(self, '_user'): self._user = self._authenticate() return self._user + def _authenticate(self): """ @@ -351,6 +393,7 @@ class AuthMixin(object): return user return AnonymousUser() + # TODO: wrap this behavior around dispatch() def _check_permissions(self): """ @@ -359,7 +402,7 @@ class AuthMixin(object): user = self.user for permission_cls in self.permissions: permission = permission_cls(self) - permission.check_permission(user) + permission.check_permission(user) ########## Resource Mixin ########## diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index 9e1b971bb..4337098af 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -111,7 +111,8 @@ class PlainTextParser(BaseParser): class FormParser(BaseParser, DataFlatener): - """The default parser for form data. + """ + The default parser for form data. Return a dict containing a single value for each non-reserved parameter. In order to handle select multiple (and having possibly more than a single value for each parameter), @@ -122,7 +123,8 @@ class FormParser(BaseParser, DataFlatener): """The value of the parameter when the select multiple is empty. Browsers are usually stripping the select multiple that have no option selected from the parameters sent. A common hack to avoid this is to send the parameter with a value specifying that the list is empty. - This value will always be stripped before the data is returned.""" + This value will always be stripped before the data is returned. + """ EMPTY_VALUE = '_empty' RESERVED_FORM_PARAMS = ('csrfmiddlewaretoken',) diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py index e8763f349..371b5ef0e 100644 --- a/djangorestframework/renderers.py +++ b/djangorestframework/renderers.py @@ -7,6 +7,7 @@ and providing forms and links depending on the allowed methods, renderers and pa """ from django import forms from django.conf import settings +from django.core.serializers.json import DateTimeAwareJSONEncoder from django.template import RequestContext, loader from django.utils import simplejson as json @@ -81,7 +82,7 @@ class JSONRenderer(BaseRenderer): except (ValueError, TypeError): indent = None - return json.dumps(obj, indent=indent, sort_keys=sort_keys) + return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys) class XMLRenderer(BaseRenderer): diff --git a/djangorestframework/resource.py b/djangorestframework/resources.py similarity index 97% rename from djangorestframework/resource.py rename to djangorestframework/resources.py index 775d52889..f47b41d0e 100644 --- a/djangorestframework/resource.py +++ b/djangorestframework/resources.py @@ -16,9 +16,14 @@ def _model_to_dict(instance, fields=None, exclude=None): """ opts = instance._meta data = {} + + #print [rel.name for rel in opts.get_all_related_objects()] + #related = [rel.get_accessor_name() for rel in opts.get_all_related_objects()] + #print [getattr(instance, rel) for rel in related] + for f in opts.fields + opts.many_to_many: - if not f.editable: - continue + #if not f.editable: + # continue if fields and not f.name in fields: continue if exclude and f.name in exclude: @@ -27,6 +32,15 @@ def _model_to_dict(instance, fields=None, exclude=None): data[f.name] = getattr(instance, f.name) else: data[f.name] = f.value_from_object(instance) + + #print fields - (opts.fields + opts.many_to_many) + #for related in [rel.get_accessor_name() for rel in opts.get_all_related_objects()]: + # if fields and not related in fields: + # continue + # if exclude and related in exclude: + # continue + # data[related] = getattr(instance, related) + return data @@ -127,6 +141,8 @@ class Resource(BaseResource): A (horrible) munging of Piston's pre-serialization. Returns a dict. """ + return _object_to_data(obj) + def _any(thing, fields=()): """ Dispatch, all types are routed through here. diff --git a/djangorestframework/tests/resource.py b/djangorestframework/tests/resources.py similarity index 94% rename from djangorestframework/tests/resource.py rename to djangorestframework/tests/resources.py index 0ed41951b..6aa569d34 100644 --- a/djangorestframework/tests/resource.py +++ b/djangorestframework/tests/resources.py @@ -1,6 +1,6 @@ """Tests for the resource module""" from django.test import TestCase -from djangorestframework.resource import _object_to_data +from djangorestframework.resources import _object_to_data import datetime import decimal diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 211dafca4..315c25a93 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -56,9 +56,10 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View): """ return [method.upper() for method in self.http_method_names if hasattr(self, method)] + 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. + 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}) @@ -68,56 +69,48 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View): # all other authentication is CSRF exempt. @csrf_exempt def dispatch(self, request, *args, **kwargs): - try: - self.request = request - self.args = args - self.kwargs = kwargs + 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: + # 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.object_to_data(response.raw_content) - # 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 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.object_to_data(response.raw_content) - - except ErrorResponse, exc: - response = exc.response - except: - import traceback - traceback.print_exc() - raise - - # 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.render(response) - except: - import traceback - traceback.print_exc() + 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.render(response)