mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	Cleanups - line lengths, whitespace etc.
This commit is contained in:
		
							parent
							
								
									db6df5ce61
								
							
						
					
					
						commit
						f84fd47825
					
				|  | @ -1,15 +1,17 @@ | ||||||
| """ | """ | ||||||
| The :mod:`authentication` module provides a set of pluggable authentication classes. | The :mod:`authentication` module provides a set of pluggable authentication | ||||||
|  | classes. | ||||||
| 
 | 
 | ||||||
| Authentication behavior is provided by mixing the :class:`mixins.AuthMixin` class into a :class:`View` class. | Authentication behavior is provided by mixing the :class:`mixins.AuthMixin` | ||||||
|  | class into a :class:`View` class. | ||||||
| 
 | 
 | ||||||
| The set of authentication methods which are used is then specified by setting the | The set of authentication methods which are used is then specified by setting | ||||||
| :attr:`authentication` attribute on the :class:`View` class, and listing a set of :class:`authentication` classes. | the :attr:`authentication` attribute on the :class:`View` class, and listing a | ||||||
|  | set of :class:`authentication` classes. | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from django.contrib.auth import authenticate | from django.contrib.auth import authenticate | ||||||
| from django.middleware.csrf import CsrfViewMiddleware | from django.middleware.csrf import CsrfViewMiddleware | ||||||
| from djangorestframework.utils import as_tuple |  | ||||||
| import base64 | import base64 | ||||||
| 
 | 
 | ||||||
| __all__ = ( | __all__ = ( | ||||||
|  | @ -26,23 +28,25 @@ class BaseAuthentication(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, view): |     def __init__(self, view): | ||||||
|         """ |         """ | ||||||
|         :class:`Authentication` classes are always passed the current view on creation. |         :class:`Authentication` classes are always passed the current view on | ||||||
|  |         creation. | ||||||
|         """ |         """ | ||||||
|         self.view = view |         self.view = view | ||||||
| 
 | 
 | ||||||
|     def authenticate(self, request): |     def authenticate(self, request): | ||||||
|         """ |         """ | ||||||
|         Authenticate the :obj:`request` and return a :obj:`User` or :const:`None`. [*]_ |         Authenticate the :obj:`request` and return a :obj:`User` or | ||||||
|  |         :const:`None`. [*]_ | ||||||
| 
 | 
 | ||||||
|         .. [*] The authentication context *will* typically be a :obj:`User`, |         .. [*] The authentication context *will* typically be a :obj:`User`, | ||||||
|             but it need not be.  It can be any user-like object so long as the |             but it need not be.  It can be any user-like object so long as the | ||||||
|             permissions classes (see the :mod:`permissions` module) on the view can |             permissions classes (see the :mod:`permissions` module) on the view | ||||||
|             handle the object and use it to determine if the request has the required |             can handle the object and use it to determine if the request has | ||||||
|             permissions or not.  |             the required permissions or not. | ||||||
| 
 | 
 | ||||||
|             This can be an important distinction if you're implementing some token |             This can be an important distinction if you're implementing some | ||||||
|             based authentication mechanism, where the authentication context |             token based authentication mechanism, where the authentication | ||||||
|             may be more involved than simply mapping to a :obj:`User`. |             context may be more involved than simply mapping to a :obj:`User`. | ||||||
|         """ |         """ | ||||||
|         return None |         return None | ||||||
| 
 | 
 | ||||||
|  | @ -51,13 +55,19 @@ class BasicAuthentication(BaseAuthentication): | ||||||
|     """ |     """ | ||||||
|     Use HTTP Basic authentication. |     Use HTTP Basic authentication. | ||||||
|     """ |     """ | ||||||
|  |     def _authenticate_user(self, username, password): | ||||||
|  |         user = authenticate(username=username, password=password) | ||||||
|  |         if user and user.is_active: | ||||||
|  |             return user | ||||||
|  |         return None | ||||||
| 
 | 
 | ||||||
|     def authenticate(self, request): |     def authenticate(self, request): | ||||||
|         """ |         """ | ||||||
|         Returns a :obj:`User` if a correct username and password have been supplied |         Returns a :obj:`User` if a correct username and password have been | ||||||
|         using HTTP Basic authentication.  Otherwise returns :const:`None`.   |         supplied using HTTP Basic authentication. | ||||||
|  |         Otherwise returns :const:`None`. | ||||||
|         """ |         """ | ||||||
|         from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError |         from django.utils import encoding | ||||||
| 
 | 
 | ||||||
|         if 'HTTP_AUTHORIZATION' in request.META: |         if 'HTTP_AUTHORIZATION' in request.META: | ||||||
|             auth = request.META['HTTP_AUTHORIZATION'].split() |             auth = request.META['HTTP_AUTHORIZATION'].split() | ||||||
|  | @ -68,13 +78,15 @@ class BasicAuthentication(BaseAuthentication): | ||||||
|                     return None |                     return None | ||||||
| 
 | 
 | ||||||
|                 try: |                 try: | ||||||
|                     uname, passwd = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2]) |                     username = encoding.smart_unicode(auth_parts[0]) | ||||||
|                 except DjangoUnicodeDecodeError: |                     password = encoding.smart_unicode(auth_parts[2]) | ||||||
|  |                 except encoding.DjangoUnicodeDecodeError: | ||||||
|                     return None |                     return None | ||||||
| 
 | 
 | ||||||
|                 user = authenticate(username=uname, password=passwd) |                 user = self._authenticate_user(username, password) | ||||||
|                 if user is not None and user.is_active: |                 if user: | ||||||
|                     return user |                     return user | ||||||
|  | 
 | ||||||
|         return None |         return None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -85,10 +97,11 @@ class UserLoggedInAuthentication(BaseAuthentication): | ||||||
| 
 | 
 | ||||||
|     def authenticate(self, request): |     def authenticate(self, request): | ||||||
|         """ |         """ | ||||||
|         Returns a :obj:`User` if the request session currently has a logged in user. |         Returns a :obj:`User` if the request session currently has a logged in | ||||||
|         Otherwise returns :const:`None`. |         user. Otherwise returns :const:`None`. | ||||||
|         """ |         """ | ||||||
|         # TODO: Switch this back to request.POST, and let FormParser/MultiPartParser deal with the consequences. |         # TODO: Switch this back to request.POST, and let | ||||||
|  |         # FormParser/MultiPartParser deal with the consequences. | ||||||
|         if getattr(request, 'user', None) and request.user.is_active: |         if getattr(request, 'user', None) and request.user.is_active: | ||||||
|             # If this is a POST request we enforce CSRF validation. |             # If this is a POST request we enforce CSRF validation. | ||||||
|             if request.method.upper() == 'POST': |             if request.method.upper() == 'POST': | ||||||
|  |  | ||||||
|  | @ -5,13 +5,12 @@ classes that can be added to a `View`. | ||||||
| 
 | 
 | ||||||
| from django.contrib.auth.models import AnonymousUser | from django.contrib.auth.models import AnonymousUser | ||||||
| from django.core.paginator import Paginator | from django.core.paginator import Paginator | ||||||
| from django.db.models.fields.related import ForeignKey |  | ||||||
| from django.http import HttpResponse | from django.http import HttpResponse | ||||||
| 
 | 
 | ||||||
| from djangorestframework import status | from djangorestframework import status | ||||||
|  | from djangorestframework.renderers import BaseRenderer | ||||||
| from djangorestframework.resources import Resource, FormResource, ModelResource | from djangorestframework.resources import Resource, FormResource, ModelResource | ||||||
| from djangorestframework.response import ErrorResponse | from djangorestframework.response import Response, ErrorResponse | ||||||
| from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX |  | ||||||
| from djangorestframework.utils import MSIE_USER_AGENT_REGEX | from djangorestframework.utils import MSIE_USER_AGENT_REGEX | ||||||
| from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence | from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence | ||||||
| 
 | 
 | ||||||
|  | @ -55,14 +54,14 @@ class RequestMixin(object): | ||||||
|         """ |         """ | ||||||
|         Returns the HTTP method. |         Returns the HTTP method. | ||||||
| 
 | 
 | ||||||
|         This should be used instead of just reading :const:`request.method`, as it allows the `method` |         This should be used instead of just reading :const:`request.method`, as | ||||||
|         to be overridden by using a hidden `form` field on a form POST request. |         it allows the `method` to be overridden by using a hidden `form` field | ||||||
|  |         on a form POST request. | ||||||
|         """ |         """ | ||||||
|         if not hasattr(self, '_method'): |         if not hasattr(self, '_method'): | ||||||
|             self._load_method_and_content_type() |             self._load_method_and_content_type() | ||||||
|         return self._method |         return self._method | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def content_type(self): |     def content_type(self): | ||||||
|         """ |         """ | ||||||
|  | @ -76,7 +75,6 @@ class RequestMixin(object): | ||||||
|             self._load_method_and_content_type() |             self._load_method_and_content_type() | ||||||
|         return self._content_type |         return self._content_type | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def DATA(self): |     def DATA(self): | ||||||
|         """ |         """ | ||||||
|  | @ -89,7 +87,6 @@ class RequestMixin(object): | ||||||
|             self._load_data_and_files() |             self._load_data_and_files() | ||||||
|         return self._data |         return self._data | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def FILES(self): |     def FILES(self): | ||||||
|         """ |         """ | ||||||
|  | @ -101,7 +98,6 @@ class RequestMixin(object): | ||||||
|             self._load_data_and_files() |             self._load_data_and_files() | ||||||
|         return self._files |         return self._files | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def _load_data_and_files(self): |     def _load_data_and_files(self): | ||||||
|         """ |         """ | ||||||
|         Parse the request content into self.DATA and self.FILES. |         Parse the request content into self.DATA and self.FILES. | ||||||
|  | @ -110,18 +106,19 @@ class RequestMixin(object): | ||||||
|             self._load_method_and_content_type() |             self._load_method_and_content_type() | ||||||
| 
 | 
 | ||||||
|         if not hasattr(self, '_data'): |         if not hasattr(self, '_data'): | ||||||
|             (self._data, self._files) = self._parse(self._get_stream(), self._content_type) |             (self._data, self._files) = self._parse(self._get_stream(), | ||||||
| 
 |                                                     self._content_type) | ||||||
| 
 | 
 | ||||||
|     def _load_method_and_content_type(self): |     def _load_method_and_content_type(self): | ||||||
|         """ |         """ | ||||||
|         Set the method and content_type, and then check if they've been overridden. |         Set the method and content_type, and then check if they've been | ||||||
|  |         overridden. | ||||||
|         """ |         """ | ||||||
|         self._method = self.request.method |         self._method = self.request.method | ||||||
|         self._content_type = self.request.META.get('HTTP_CONTENT_TYPE', self.request.META.get('CONTENT_TYPE', '')) |         self._content_type = self.request.META.get('HTTP_CONTENT_TYPE', | ||||||
|  |                                 self.request.META.get('CONTENT_TYPE', '')) | ||||||
|         self._perform_form_overloading() |         self._perform_form_overloading() | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def _get_stream(self): |     def _get_stream(self): | ||||||
|         """ |         """ | ||||||
|         Returns an object that may be used to stream the request content. |         Returns an object that may be used to stream the request content. | ||||||
|  | @ -129,7 +126,8 @@ class RequestMixin(object): | ||||||
|         request = self.request |         request = self.request | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             content_length = int(request.META.get('CONTENT_LENGTH', request.META.get('HTTP_CONTENT_LENGTH'))) |             content_length = int(request.META.get('CONTENT_LENGTH', | ||||||
|  |                                     request.META.get('HTTP_CONTENT_LENGTH'))) | ||||||
|         except (ValueError, TypeError): |         except (ValueError, TypeError): | ||||||
|             content_length = 0 |             content_length = 0 | ||||||
| 
 | 
 | ||||||
|  | @ -141,15 +139,17 @@ class RequestMixin(object): | ||||||
|             return request |             return request | ||||||
|         return StringIO(request.raw_post_data) |         return StringIO(request.raw_post_data) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def _perform_form_overloading(self): |     def _perform_form_overloading(self): | ||||||
|         """ |         """ | ||||||
|         If this is a form POST request, then we need to check if the method and content/content_type have been |         If this is a form POST request, then we need to check if the method and | ||||||
|         overridden by setting them in hidden form fields or not. |         content/content_type have been overridden by setting them in hidden | ||||||
|  |         form fields or not. | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         # We only need to use form overloading on form POST requests. |         # We only need to use form overloading on form POST requests. | ||||||
|         if not self._USE_FORM_OVERLOADING or self._method != 'POST' or not is_form_media_type(self._content_type): |         if (not self._USE_FORM_OVERLOADING | ||||||
|  |             or self._method != 'POST' | ||||||
|  |             or not is_form_media_type(self._content_type)): | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         # At this point we're committed to parsing the request as form data. |         # At this point we're committed to parsing the request as form data. | ||||||
|  | @ -167,26 +167,24 @@ class RequestMixin(object): | ||||||
|             stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0]) |             stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0]) | ||||||
|             (self._data, self._files) = self._parse(stream, self._content_type) |             (self._data, self._files) = self._parse(stream, self._content_type) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def _parse(self, stream, content_type): |     def _parse(self, stream, content_type): | ||||||
|         """ |         """ | ||||||
|         Parse the request content. |         Parse the request content. | ||||||
| 
 | 
 | ||||||
|         May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 ErrorResponse (Bad Request). |         May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 | ||||||
|  |         ErrorResponse (Bad Request). | ||||||
|         """ |         """ | ||||||
|         if stream is None or content_type is None: |         if stream is None or content_type is None: | ||||||
|             return (None, None) |             return (None, None) | ||||||
| 
 | 
 | ||||||
|         parsers = as_tuple(self.parsers) |  | ||||||
|         for parser_cls in self.parsers: |         for parser_cls in self.parsers: | ||||||
|             parser = parser_cls(self) |             parser = parser_cls(self) | ||||||
|             if parser.can_handle_request(content_type): |             if parser.can_handle_request(content_type): | ||||||
|                 return parser.parse(stream) |                 return parser.parse(stream) | ||||||
| 
 | 
 | ||||||
|         raise ErrorResponse(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, |         error = {'error': | ||||||
|                             {'error': 'Unsupported media type in request \'%s\'.' % |                  "Unsupported media type in request '%s'." % content_type} | ||||||
|                             content_type}) |         raise ErrorResponse(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, error) | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def _parsed_media_types(self): |     def _parsed_media_types(self): | ||||||
|  | @ -195,7 +193,6 @@ class RequestMixin(object): | ||||||
|         """ |         """ | ||||||
|         return [parser.media_type for parser in self.parsers] |         return [parser.media_type for parser in self.parsers] | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def _default_parser(self): |     def _default_parser(self): | ||||||
|         """ |         """ | ||||||
|  | @ -204,29 +201,34 @@ class RequestMixin(object): | ||||||
|         return self.parsers[0] |         return self.parsers[0] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| ########## ResponseMixin ########## | ########## ResponseMixin ########## | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class ResponseMixin(object): | class ResponseMixin(object): | ||||||
|     """ |     """ | ||||||
|     Adds behavior for pluggable `Renderers` to a :class:`views.View` class. |     Adds behavior for pluggable `Renderers` to a :class:`views.View` class. | ||||||
| 
 | 
 | ||||||
|     Default behavior is to use standard HTTP Accept header content negotiation. |     Default behavior is to use standard HTTP Accept header content negotiation. | ||||||
|     Also supports overriding the content type by specifying an ``_accept=`` parameter in the URL. | 
 | ||||||
|     Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead. |     Also supports overriding the content type by specifying an ``_accept=`` | ||||||
|  |     parameter in the URL. | ||||||
|  | 
 | ||||||
|  |     Ignores Accept headers from Internet Explorer user agents and uses a | ||||||
|  |     sensible browser Accept header instead. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     _ACCEPT_QUERY_PARAM = '_accept'        # Allow override of Accept header in URL query params |     # Allow override of Accept header in URL query params | ||||||
|  |     _ACCEPT_QUERY_PARAM = '_accept' | ||||||
|     _IGNORE_IE_ACCEPT_HEADER = True |     _IGNORE_IE_ACCEPT_HEADER = True | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     The set of response renderers that the view can handle. |     The set of response renderers that the view can handle. | ||||||
| 
 | 
 | ||||||
|     Should be a tuple/list of classes as described in the :mod:`renderers` module. |     Should be a tuple/list of classes as described in the :mod:`renderers` | ||||||
|  |     module. | ||||||
|     """ |     """ | ||||||
|     renderers = () |     renderers = () | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     # TODO: wrap this behavior around dispatch(), ensuring it works |     # TODO: wrap this behavior around dispatch(), ensuring it works | ||||||
|     # out of the box with existing Django classes that use render_to_response. |     # out of the box with existing Django classes that use render_to_response. | ||||||
|     def render(self, response): |     def render(self, response): | ||||||
|  | @ -253,13 +255,13 @@ class ResponseMixin(object): | ||||||
|             content = renderer.render() |             content = renderer.render() | ||||||
| 
 | 
 | ||||||
|         # Build the HTTP Response |         # Build the HTTP Response | ||||||
|         resp = HttpResponse(content, mimetype=response.media_type, status=response.status) |         resp = HttpResponse(content, mimetype=response.media_type, | ||||||
|  |                             status=response.status) | ||||||
|         for (key, val) in response.headers.items(): |         for (key, val) in response.headers.items(): | ||||||
|             resp[key] = val |             resp[key] = val | ||||||
| 
 | 
 | ||||||
|         return resp |         return resp | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def _determine_renderer(self, request): |     def _determine_renderer(self, request): | ||||||
|         """ |         """ | ||||||
|         Determines the appropriate renderer for the output, given the client's |         Determines the appropriate renderer for the output, given the client's | ||||||
|  | @ -283,10 +285,10 @@ class ResponseMixin(object): | ||||||
|             # instead. |             # instead. | ||||||
|             accept_list = ['text/html', '*/*'] |             accept_list = ['text/html', '*/*'] | ||||||
| 
 | 
 | ||||||
|         elif 'HTTP_USER_AGENT' in request.META: |         elif 'HTTP_ACCEPT' in request.META: | ||||||
|             # Use standard HTTP Accept negotiation |             # Use standard HTTP Accept negotiation | ||||||
|             accept_list = [token.strip() for token in |             accept_list = [token.strip() for token in | ||||||
|                            request.META["HTTP_ACCEPT"].split(',')] |                            request.META['HTTP_ACCEPT'].split(',')] | ||||||
| 
 | 
 | ||||||
|         else: |         else: | ||||||
|             # No accept header specified |             # No accept header specified | ||||||
|  | @ -295,7 +297,7 @@ class ResponseMixin(object): | ||||||
|         # Check the acceptable media types against each renderer, |         # Check the acceptable media types against each renderer, | ||||||
|         # attempting more specific media types first |         # attempting more specific media types first | ||||||
|         # NB. The inner loop here isn't as bad as it first looks :) |         # NB. The inner loop here isn't as bad as it first looks :) | ||||||
|         #     Worst case is we're looping over len(accept_list) * len(self.renderers) |         #     Worst case is: len(accept_list) * len(self.renderers) | ||||||
|         renderers = [renderer_cls(self) for renderer_cls in self.renderers] |         renderers = [renderer_cls(self) for renderer_cls in self.renderers] | ||||||
| 
 | 
 | ||||||
|         for accepted_media_type_lst in order_by_precedence(accept_list): |         for accepted_media_type_lst in order_by_precedence(accept_list): | ||||||
|  | @ -305,10 +307,9 @@ class ResponseMixin(object): | ||||||
|                         return renderer, accepted_media_type |                         return renderer, accepted_media_type | ||||||
| 
 | 
 | ||||||
|         # No acceptable renderers were found |         # No acceptable renderers were found | ||||||
|         raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE, |         error = {'detail': "Could not satisfy the client's Accept header", | ||||||
|                                 {'detail': 'Could not satisfy the client\'s Accept header', |                  'available_types': self._rendered_media_types} | ||||||
|                                  'available_types': self._rendered_media_types}) |         raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE, error) | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def _rendered_media_types(self): |     def _rendered_media_types(self): | ||||||
|  | @ -336,39 +337,40 @@ class ResponseMixin(object): | ||||||
| 
 | 
 | ||||||
| class AuthMixin(object): | class AuthMixin(object): | ||||||
|     """ |     """ | ||||||
|     Simple :class:`mixin` class to add authentication and permission checking to a :class:`View` class. |     Simple :class:`mixin` class to add authentication and permission checking | ||||||
|  |     to a :class:`View` class. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     The set of authentication types that this view can handle. |     The set of authentication types that this view can handle. | ||||||
| 
 | 
 | ||||||
|     Should be a tuple/list of classes as described in the :mod:`authentication` module. |     Should be a tuple/list of classes as described in the :mod:`authentication` | ||||||
|  |     module. | ||||||
|     """ |     """ | ||||||
|     authentication = () |     authentication = () | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     The set of permissions that will be enforced on this view. |     The set of permissions that will be enforced on this view. | ||||||
| 
 | 
 | ||||||
|     Should be a tuple/list of classes as described in the :mod:`permissions` module. |     Should be a tuple/list of classes as described in the :mod:`permissions` | ||||||
|  |     module. | ||||||
|     """ |     """ | ||||||
|     permissions = () |     permissions = () | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     @property |     @property | ||||||
|     def user(self): |     def user(self): | ||||||
|         """ |         """ | ||||||
|         Returns the :obj:`user` for the current request, as determined by the set of |         Returns the :obj:`user` for the current request, as determined by the | ||||||
|         :class:`authentication` classes applied to the :class:`View`. |         set of :class:`authentication` classes applied to the :class:`View`. | ||||||
|         """ |         """ | ||||||
|         if not hasattr(self, '_user'): |         if not hasattr(self, '_user'): | ||||||
|             self._user = self._authenticate() |             self._user = self._authenticate() | ||||||
|         return self._user |         return self._user | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def _authenticate(self): |     def _authenticate(self): | ||||||
|         """ |         """ | ||||||
|         Attempt to authenticate the request using each authentication class in turn. |         Attempt to authenticate the request using each authentication class in | ||||||
|         Returns a ``User`` object, which may be ``AnonymousUser``. |         turn.  Returns a ``User`` object, which may be ``AnonymousUser``. | ||||||
|         """ |         """ | ||||||
|         for authentication_cls in self.authentication: |         for authentication_cls in self.authentication: | ||||||
|             authentication = authentication_cls(self) |             authentication = authentication_cls(self) | ||||||
|  | @ -377,7 +379,6 @@ class AuthMixin(object): | ||||||
|                 return user |                 return user | ||||||
|         return AnonymousUser() |         return AnonymousUser() | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     # TODO: wrap this behavior around dispatch() |     # TODO: wrap this behavior around dispatch() | ||||||
|     def _check_permissions(self): |     def _check_permissions(self): | ||||||
|         """ |         """ | ||||||
|  | @ -397,10 +398,12 @@ class ResourceMixin(object): | ||||||
| 
 | 
 | ||||||
|     Should be a class as described in the :mod:`resources` module. |     Should be a class as described in the :mod:`resources` module. | ||||||
| 
 | 
 | ||||||
|     The :obj:`resource` is an object that maps a view onto it's representation on the server. |     The :obj:`resource` is an object that maps a view onto it's representation | ||||||
|  |     on the server. | ||||||
| 
 | 
 | ||||||
|     It provides validation on the content of incoming requests, |     It provides validation on the content of incoming requests, | ||||||
|     and filters the object representation into a serializable object for the response. |     and filters the object representation into a serializable object for the | ||||||
|  |     response. | ||||||
|     """ |     """ | ||||||
|     resource = None |     resource = None | ||||||
| 
 | 
 | ||||||
|  | @ -409,7 +412,8 @@ class ResourceMixin(object): | ||||||
|         """ |         """ | ||||||
|         Returns the cleaned, validated request content. |         Returns the cleaned, validated request content. | ||||||
| 
 | 
 | ||||||
|         May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request). |         May raise an :class:`response.ErrorResponse` with status code 400 | ||||||
|  |         (Bad Request). | ||||||
|         """ |         """ | ||||||
|         if not hasattr(self, '_content'): |         if not hasattr(self, '_content'): | ||||||
|             self._content = self.validate_request(self.DATA, self.FILES) |             self._content = self.validate_request(self.DATA, self.FILES) | ||||||
|  | @ -420,7 +424,8 @@ class ResourceMixin(object): | ||||||
|         """ |         """ | ||||||
|         Returns the cleaned, validated query parameters. |         Returns the cleaned, validated query parameters. | ||||||
| 
 | 
 | ||||||
|         May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request). |         May raise an :class:`response.ErrorResponse` with status code 400 | ||||||
|  |         (Bad Request). | ||||||
|         """ |         """ | ||||||
|         return self.validate_request(self.request.GET) |         return self.validate_request(self.request.GET) | ||||||
| 
 | 
 | ||||||
|  | @ -438,8 +443,10 @@ class ResourceMixin(object): | ||||||
| 
 | 
 | ||||||
|     def validate_request(self, data, files=None): |     def validate_request(self, data, files=None): | ||||||
|         """ |         """ | ||||||
|         Given the request *data* and optional *files*, return the cleaned, validated content. |         Given the request *data* and optional *files*, return the cleaned, | ||||||
|         May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure. |         validated content. | ||||||
|  |         May raise an :class:`response.ErrorResponse` with status code 400 | ||||||
|  |         (Bad Request) on failure. | ||||||
|         """ |         """ | ||||||
|         return self._resource.validate_request(data, files) |         return self._resource.validate_request(data, files) | ||||||
| 
 | 
 | ||||||
|  | @ -456,26 +463,28 @@ class ResourceMixin(object): | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| ########## | ########## | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class InstanceMixin(object): | class InstanceMixin(object): | ||||||
|     """ |     """ | ||||||
|     `Mixin` class that is used to identify a `View` class as being the canonical identifier |     `Mixin` class that is used to identify a `View` class as being the | ||||||
|     for the resources it is mapped to. |     canonical identifier for the resources it is mapped to. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def as_view(cls, **initkwargs): |     def as_view(cls, **initkwargs): | ||||||
|         """ |         """ | ||||||
|         Store the callable object on the resource class that has been associated with this view. |         Store the callable object on the resource class that has been | ||||||
|  |         associated with this view. | ||||||
|         """ |         """ | ||||||
|         view = super(InstanceMixin, cls).as_view(**initkwargs) |         view = super(InstanceMixin, cls).as_view(**initkwargs) | ||||||
|         resource = getattr(cls(**initkwargs), 'resource', None) |         resource = getattr(cls(**initkwargs), 'resource', None) | ||||||
|         if resource: |         if resource: | ||||||
|             # We do a little dance when we store the view callable... |             # We do a little dance when we store the view callable... | ||||||
|             # we need to store it wrapped in a 1-tuple, so that inspect will treat it |             # we need to store it wrapped in a 1-tuple, so that inspect will | ||||||
|             # as a function when we later look it up (rather than turning it into a method). |             # treat it as a function when we later look it up (rather than | ||||||
|  |             # turning it into a method). | ||||||
|             # This makes sure our URL reversing works ok. |             # This makes sure our URL reversing works ok. | ||||||
|             resource.view_callable = (view,) |             resource.view_callable = (view,) | ||||||
|         return view |         return view | ||||||
|  | @ -483,6 +492,7 @@ class InstanceMixin(object): | ||||||
| 
 | 
 | ||||||
| ########## Model Mixins ########## | ########## Model Mixins ########## | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class ModelMixin(object): | class ModelMixin(object): | ||||||
|     def get_model(self): |     def get_model(self): | ||||||
|         """ |         """ | ||||||
|  | @ -497,7 +507,7 @@ class ModelMixin(object): | ||||||
|         """ |         """ | ||||||
|         return getattr(self, 'queryset', |         return getattr(self, 'queryset', | ||||||
|                     getattr(self.resource, 'queryset', |                     getattr(self.resource, 'queryset', | ||||||
|                         self._get_model().objects.all())) |                         self.get_model().objects.all())) | ||||||
| 
 | 
 | ||||||
|     def get_ordering(self): |     def get_ordering(self): | ||||||
|         """ |         """ | ||||||
|  | @ -507,73 +517,32 @@ class ModelMixin(object): | ||||||
|                     getattr(self.resource, 'ordering', |                     getattr(self.resource, 'ordering', | ||||||
|                         None)) |                         None)) | ||||||
| 
 | 
 | ||||||
|  |     # Underlying instance API... | ||||||
|  | 
 | ||||||
|     def get_instance(self, *args, **kwargs): |     def get_instance(self, *args, **kwargs): | ||||||
|         """ |         """ | ||||||
|         Return a model instance or None. |         Return a model instance or None. | ||||||
|         """ |         """ | ||||||
|         model = self.get_model() |         model = self.get_model() | ||||||
|         queryset = self.get_queryset() |         queryset = self.get_queryset() | ||||||
|         kwargs = self._filter_kwargs(kwargs) |  | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             # If we have any positional args then assume the last |  | ||||||
|             # represents the primary key.  Otherwise assume the named kwargs |  | ||||||
|             # uniquely identify the instance. |  | ||||||
|             if args: |  | ||||||
|                 return queryset.get(pk=args[-1], **kwargs) |  | ||||||
|             else: |  | ||||||
|             return queryset.get(**kwargs) |             return queryset.get(**kwargs) | ||||||
|         except model.DoesNotExist: |         except model.DoesNotExist: | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|     def read(self, request, *args, **kwargs): |     def create_instance(self, *args, **kwargs): | ||||||
|         instance = self.get_instance(*args, **kwargs) |         model = self.get_model() | ||||||
|         return instance |  | ||||||
| 
 | 
 | ||||||
|     def update(self, request, *args, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Return a model instance. |  | ||||||
|         """ |  | ||||||
|         instance = self.get_instance(*args, **kwargs) |  | ||||||
| 
 |  | ||||||
|         if instance: |  | ||||||
|             for (key, val) in self.CONTENT.items(): |  | ||||||
|                 setattr(instance, key, val) |  | ||||||
|         else: |  | ||||||
|             instance = self.get_model()(**self.CONTENT) |  | ||||||
| 
 |  | ||||||
|         instance.save() |  | ||||||
|         return instance |  | ||||||
| 
 |  | ||||||
|     def create(self, request, *args, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Return a model instance. |  | ||||||
|         """ |  | ||||||
|         model = self._get_model() |  | ||||||
| 
 |  | ||||||
|         # Copy the dict to keep self.CONTENT intact |  | ||||||
|         content = dict(self.CONTENT) |  | ||||||
|         m2m_data = {} |         m2m_data = {} | ||||||
| 
 |         for field in model._meta.many_to_many: | ||||||
|         for field in model._meta.fields: |             if field.name in kwargs: | ||||||
|             if isinstance(field, ForeignKey) and field.name in kwargs: |                 m2m_data[field.name] = ( | ||||||
|                 # translate 'related_field' kwargs into 'related_field_id' |                     field.m2m_reverse_field_name(), kwargs[field.name] | ||||||
|                 kwargs[field.name + '_id'] = kwargs[field.name] |                 ) | ||||||
|                 del kwargs[field.name] |                 del kwargs[field.name] | ||||||
| 
 | 
 | ||||||
|         for field in model._meta.many_to_many: |         instance = model(**kwargs) | ||||||
|             if field.name in content: |  | ||||||
|                 m2m_data[field.name] = ( |  | ||||||
|                     field.m2m_reverse_field_name(), content[field.name] |  | ||||||
|                 ) |  | ||||||
|                 del content[field.name] |  | ||||||
| 
 |  | ||||||
|         all_kw_args = dict(content.items() + kwargs.items()) |  | ||||||
| 
 |  | ||||||
|         if args: |  | ||||||
|             instance = model(pk=args[-1], **all_kw_args) |  | ||||||
|         else: |  | ||||||
|             instance = model(**all_kw_args) |  | ||||||
|         instance.save() |         instance.save() | ||||||
| 
 | 
 | ||||||
|         for fieldname in m2m_data: |         for fieldname in m2m_data: | ||||||
|  | @ -591,31 +560,81 @@ class ModelMixin(object): | ||||||
| 
 | 
 | ||||||
|         return instance |         return instance | ||||||
| 
 | 
 | ||||||
| 
 |     def update_instance(self, instance, *args, **kwargs): | ||||||
|     def destroy(self, request, *args, **kwargs): |         for (key, val) in kwargs.items(): | ||||||
|         """ |             setattr(instance, key, val) | ||||||
|         Return a model instance or None. |         instance.save() | ||||||
|         """ |  | ||||||
|         instance = self.get_instance(*args, **kwargs) |  | ||||||
| 
 |  | ||||||
|         if instance: |  | ||||||
|             instance.delete() |  | ||||||
| 
 |  | ||||||
|         return instance |         return instance | ||||||
| 
 | 
 | ||||||
|  |     def delete_instance(self, instance, *args, **kwargs): | ||||||
|  |         instance.delete() | ||||||
|  |         return instance | ||||||
| 
 | 
 | ||||||
|     def list(self, request, *args, **kwargs): |     def list_instances(self, *args, **kwargs): | ||||||
|         """ |  | ||||||
|         Return a list of instances. |  | ||||||
|         """ |  | ||||||
|         queryset = self.get_queryset() |         queryset = self.get_queryset() | ||||||
|         ordering = self.get_ordering() |         ordering = self.get_ordering() | ||||||
| 
 | 
 | ||||||
|         if ordering: |         if ordering: | ||||||
|             assert(hasattr(ordering, '__iter__')) |             queryset = queryset.order_by(ordering) | ||||||
|             queryset = queryset.order_by(*args) |  | ||||||
|         return queryset.filter(**kwargs) |         return queryset.filter(**kwargs) | ||||||
| 
 | 
 | ||||||
|  |     # Request/Response layer... | ||||||
|  | 
 | ||||||
|  |     def _get_url_kwargs(self, kwargs): | ||||||
|  |         format_arg = BaseRenderer._FORMAT_QUERY_PARAM | ||||||
|  |         if format_arg in kwargs: | ||||||
|  |             kwargs = kwargs.copy() | ||||||
|  |             del kwargs[format_arg] | ||||||
|  |         return kwargs | ||||||
|  | 
 | ||||||
|  |     def _get_content_kwargs(self, kwargs): | ||||||
|  |         return dict(self._get_url_kwargs(kwargs).items() + | ||||||
|  |                     self.CONTENT.items()) | ||||||
|  | 
 | ||||||
|  |     def read(self, request, *args, **kwargs): | ||||||
|  |         kwargs = self._get_url_kwargs(kwargs) | ||||||
|  |         instance = self.get_instance(**kwargs) | ||||||
|  | 
 | ||||||
|  |         if instance is None: | ||||||
|  |             raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {}) | ||||||
|  | 
 | ||||||
|  |         return instance | ||||||
|  | 
 | ||||||
|  |     def update(self, request, *args, **kwargs): | ||||||
|  |         kwargs = self._get_url_kwargs(kwargs) | ||||||
|  |         instance = self.get_instance(**kwargs) | ||||||
|  | 
 | ||||||
|  |         kwargs = self._get_content_kwargs(kwargs) | ||||||
|  |         if instance: | ||||||
|  |             instance = self.update_instance(instance, **kwargs) | ||||||
|  |         else: | ||||||
|  |             instance = self.create_instance(**kwargs) | ||||||
|  | 
 | ||||||
|  |         return instance | ||||||
|  | 
 | ||||||
|  |     def create(self, request, *args, **kwargs): | ||||||
|  |         kwargs = self._get_content_kwargs(kwargs) | ||||||
|  |         instance = self.create_instance(**kwargs) | ||||||
|  | 
 | ||||||
|  |         headers = {} | ||||||
|  |         try: | ||||||
|  |             headers['Location'] = self.resource(self).url(instance) | ||||||
|  |         except:  # TODO: _SkipField should not really happen. | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |         return Response(status.HTTP_201_CREATED, instance, headers) | ||||||
|  | 
 | ||||||
|  |     def destroy(self, request, *args, **kwargs): | ||||||
|  |         kwargs = self._get_url_kwargs(kwargs) | ||||||
|  |         instance = self.delete_instance(**kwargs) | ||||||
|  |         if not instance: | ||||||
|  |             raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {}) | ||||||
|  | 
 | ||||||
|  |         return instance | ||||||
|  | 
 | ||||||
|  |     def list(self, request, *args, **kwargs): | ||||||
|  |         return self.list_instances(**kwargs) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ########## Pagination Mixins ########## | ########## Pagination Mixins ########## | ||||||
| 
 | 
 | ||||||
|  | @ -638,7 +657,7 @@ class PaginatorMixin(object): | ||||||
|             return self.limit |             return self.limit | ||||||
| 
 | 
 | ||||||
|     def url_with_page_number(self, page_number): |     def url_with_page_number(self, page_number): | ||||||
|         """ Constructs a url used for getting the next/previous urls """ |         """Constructs a url used for getting the next/previous urls.""" | ||||||
|         url = "%s?page=%d" % (self.request.path, page_number) |         url = "%s?page=%d" % (self.request.path, page_number) | ||||||
| 
 | 
 | ||||||
|         limit = self.get_limit() |         limit = self.get_limit() | ||||||
|  | @ -648,21 +667,21 @@ class PaginatorMixin(object): | ||||||
|         return url |         return url | ||||||
| 
 | 
 | ||||||
|     def next(self, page): |     def next(self, page): | ||||||
|         """ Returns a url to the next page of results (if any) """ |         """Returns a url to the next page of results. (If any exists.)""" | ||||||
|         if not page.has_next(): |         if not page.has_next(): | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|         return self.url_with_page_number(page.next_page_number()) |         return self.url_with_page_number(page.next_page_number()) | ||||||
| 
 | 
 | ||||||
|     def previous(self, page): |     def previous(self, page): | ||||||
|         """ Returns a url to the previous page of results (if any) """ |         """Returns a url to the previous page of results. (If any exists.)""" | ||||||
|         if not page.has_previous(): |         if not page.has_previous(): | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|         return self.url_with_page_number(page.previous_page_number()) |         return self.url_with_page_number(page.previous_page_number()) | ||||||
| 
 | 
 | ||||||
|     def serialize_page_info(self, page): |     def serialize_page_info(self, page): | ||||||
|         """ This is some useful information that is added to the response """ |         """This is some useful information that is added to the response.""" | ||||||
|         return { |         return { | ||||||
|             'next': self.next(page), |             'next': self.next(page), | ||||||
|             'page': page.number, |             'page': page.number, | ||||||
|  | @ -676,14 +695,15 @@ class PaginatorMixin(object): | ||||||
|         """ |         """ | ||||||
|         Given the response content, paginate and then serialize. |         Given the response content, paginate and then serialize. | ||||||
| 
 | 
 | ||||||
|         The response is modified to include to useful data relating to the number |         The response is modified to include to useful data relating to the | ||||||
|         of objects, number of pages, next/previous urls etc. etc. |         number of objects, number of pages, next/previous urls etc. etc. | ||||||
| 
 | 
 | ||||||
|         The serialised objects are put into `results` on this new, modified |         The serialised objects are put into `results` on this new, modified | ||||||
|         response |         response | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         # We don't want to paginate responses for anything other than GET requests |         # We don't want to paginate responses for anything other than GET | ||||||
|  |         # requests | ||||||
|         if self.method.upper() != 'GET': |         if self.method.upper() != 'GET': | ||||||
|             return self._resource.filter_response(obj) |             return self._resource.filter_response(obj) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,15 +1,13 @@ | ||||||
| from django.conf.urls.defaults import patterns, url | from django.conf.urls.defaults import patterns, url | ||||||
| from django import http |  | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| 
 | 
 | ||||||
| from djangorestframework import status | from djangorestframework import status | ||||||
| from djangorestframework.compat import View as DjangoView | from djangorestframework.compat import View as DjangoView | ||||||
| from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer,\ | from djangorestframework.renderers import BaseRenderer, JSONRenderer, \ | ||||||
|     XMLRenderer |     YAMLRenderer, XMLRenderer | ||||||
| from djangorestframework.parsers import JSONParser, YAMLParser | from djangorestframework.parsers import JSONParser, YAMLParser | ||||||
| from djangorestframework.mixins import ResponseMixin | from djangorestframework.mixins import ResponseMixin | ||||||
| from djangorestframework.response import Response | from djangorestframework.response import Response | ||||||
| from djangorestframework.utils.mediatypes import add_media_type_param |  | ||||||
| 
 | 
 | ||||||
| from StringIO import StringIO | from StringIO import StringIO | ||||||
| import datetime | import datetime | ||||||
|  | @ -21,20 +19,23 @@ DUMMYCONTENT = 'dummycontent' | ||||||
| RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x | RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x | ||||||
| RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x | RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class RendererA(BaseRenderer): | class RendererA(BaseRenderer): | ||||||
|     media_type = 'mock/renderera' |     media_type = 'mock/renderera' | ||||||
|     format="formata" |     format = "formata" | ||||||
| 
 | 
 | ||||||
|     def render(self, obj=None, media_type=None): |     def render(self, obj=None, media_type=None): | ||||||
|         return RENDERER_A_SERIALIZER(obj) |         return RENDERER_A_SERIALIZER(obj) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class RendererB(BaseRenderer): | class RendererB(BaseRenderer): | ||||||
|     media_type = 'mock/rendererb' |     media_type = 'mock/rendererb' | ||||||
|     format="formatb" |     format = "formatb" | ||||||
| 
 | 
 | ||||||
|     def render(self, obj=None, media_type=None): |     def render(self, obj=None, media_type=None): | ||||||
|         return RENDERER_B_SERIALIZER(obj) |         return RENDERER_B_SERIALIZER(obj) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class MockView(ResponseMixin, DjangoView): | class MockView(ResponseMixin, DjangoView): | ||||||
|     renderers = (RendererA, RendererB) |     renderers = (RendererA, RendererB) | ||||||
| 
 | 
 | ||||||
|  | @ -148,12 +149,7 @@ class RendererIntegrationTests(TestCase): | ||||||
| 
 | 
 | ||||||
| _flat_repr = '{"foo": ["bar", "baz"]}' | _flat_repr = '{"foo": ["bar", "baz"]}' | ||||||
| 
 | 
 | ||||||
| _indented_repr = """{ | _indented_repr = '{\n    "foo": [\n        "bar", \n        "baz"\n    ]\n}' | ||||||
|   "foo": [ |  | ||||||
|     "bar",  |  | ||||||
|     "baz" |  | ||||||
|   ] |  | ||||||
| }""" |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class JSONRendererTests(TestCase): | class JSONRendererTests(TestCase): | ||||||
|  | @ -165,7 +161,7 @@ class JSONRendererTests(TestCase): | ||||||
|         """ |         """ | ||||||
|         Test basic JSON rendering. |         Test basic JSON rendering. | ||||||
|         """ |         """ | ||||||
|         obj = {'foo':['bar','baz']} |         obj = {'foo': ['bar', 'baz']} | ||||||
|         renderer = JSONRenderer(None) |         renderer = JSONRenderer(None) | ||||||
|         content = renderer.render(obj, 'application/json') |         content = renderer.render(obj, 'application/json') | ||||||
|         self.assertEquals(content, _flat_repr) |         self.assertEquals(content, _flat_repr) | ||||||
|  | @ -174,9 +170,9 @@ class JSONRendererTests(TestCase): | ||||||
|         """ |         """ | ||||||
|         Test JSON rendering with additional content type arguments supplied. |         Test JSON rendering with additional content type arguments supplied. | ||||||
|         """ |         """ | ||||||
|         obj = {'foo':['bar','baz']} |         obj = {'foo': ['bar', 'baz']} | ||||||
|         renderer = JSONRenderer(None) |         renderer = JSONRenderer(None) | ||||||
|         content = renderer.render(obj, 'application/json; indent=2') |         content = renderer.render(obj, 'application/json; indent=4') | ||||||
|         self.assertEquals(content, _indented_repr) |         self.assertEquals(content, _indented_repr) | ||||||
| 
 | 
 | ||||||
|     def test_render_and_parse(self): |     def test_render_and_parse(self): | ||||||
|  | @ -184,7 +180,7 @@ class JSONRendererTests(TestCase): | ||||||
|         Test rendering and then parsing returns the original object. |         Test rendering and then parsing returns the original object. | ||||||
|         IE obj -> render -> parse -> obj. |         IE obj -> render -> parse -> obj. | ||||||
|         """ |         """ | ||||||
|         obj = {'foo':['bar','baz']} |         obj = {'foo': ['bar', 'baz']} | ||||||
| 
 | 
 | ||||||
|         renderer = JSONRenderer(None) |         renderer = JSONRenderer(None) | ||||||
|         parser = JSONParser(None) |         parser = JSONParser(None) | ||||||
|  | @ -194,7 +190,6 @@ class JSONRendererTests(TestCase): | ||||||
|         self.assertEquals(obj, data) |         self.assertEquals(obj, data) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| if YAMLRenderer: | if YAMLRenderer: | ||||||
|     _yaml_repr = 'foo: [bar, baz]\n' |     _yaml_repr = 'foo: [bar, baz]\n' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -50,13 +50,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): | ||||||
|     """ |     """ | ||||||
|     List of all authenticating methods to attempt. |     List of all authenticating methods to attempt. | ||||||
|     """ |     """ | ||||||
|     authentication = ( authentication.UserLoggedInAuthentication, |     authentication = (authentication.UserLoggedInAuthentication, | ||||||
|                        authentication.BasicAuthentication ) |                       authentication.BasicAuthentication) | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     List of all permissions that must be checked. |     List of all permissions that must be checked. | ||||||
|     """ |     """ | ||||||
|     permissions = ( permissions.FullAnonAccess, ) |     permissions = (permissions.FullAnonAccess,) | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def as_view(cls, **initkwargs): |     def as_view(cls, **initkwargs): | ||||||
|  | @ -86,8 +86,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): | ||||||
|     def initial(self, request, *args, **kargs): |     def initial(self, request, *args, **kargs): | ||||||
|         """ |         """ | ||||||
|         Hook for any code that needs to run prior to anything else. |         Hook for any code that needs to run prior to anything else. | ||||||
|         Required if you want to do things like set `request.upload_handlers` before |         Required if you want to do things like set `request.upload_handlers` | ||||||
|         the authentication and dispatch handling is run. |         before the authentication and dispatch handling is run. | ||||||
|         """ |         """ | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|  | @ -136,11 +136,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): | ||||||
|                 response = Response(status.HTTP_204_NO_CONTENT) |                 response = Response(status.HTTP_204_NO_CONTENT) | ||||||
| 
 | 
 | ||||||
|             if request.method == 'OPTIONS': |             if request.method == 'OPTIONS': | ||||||
|                 # do not filter the response for HTTP OPTIONS, else the response fields are lost, |                 # do not filter the response for HTTP OPTIONS, | ||||||
|  |                 # else the response fields are lost, | ||||||
|                 # as they do not correspond with model fields |                 # as they do not correspond with model fields | ||||||
|                 response.cleaned_content = response.raw_content |                 response.cleaned_content = response.raw_content | ||||||
|             else: |             else: | ||||||
|                 # Pre-serialize filtering (eg filter complex objects into natively serializable types) |                 # Pre-serialize filtering (eg filter complex objects into | ||||||
|  |                 # natively serializable types) | ||||||
|                 response.cleaned_content = self.filter_response(response.raw_content) |                 response.cleaned_content = self.filter_response(response.raw_content) | ||||||
| 
 | 
 | ||||||
|         except ErrorResponse, exc: |         except ErrorResponse, exc: | ||||||
|  | @ -148,8 +150,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): | ||||||
| 
 | 
 | ||||||
|         # Always add these headers. |         # Always add these headers. | ||||||
|         # |         # | ||||||
|         # TODO - this isn't actually the correct way to set the vary header, |         # TODO - this isn't really the correct way to set the Vary header, | ||||||
|         # also it's currently sub-optimal for HTTP caching - need to sort that out. |         # also it's currently sub-optimal for HTTP caching. | ||||||
|         response.headers['Allow'] = ', '.join(self.allowed_methods) |         response.headers['Allow'] = ', '.join(self.allowed_methods) | ||||||
|         response.headers['Vary'] = 'Authenticate, Accept' |         response.headers['Vary'] = 'Authenticate, Accept' | ||||||
| 
 | 
 | ||||||
|  | @ -161,7 +163,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): | ||||||
|         return self.render(response) |         return self.render(response) | ||||||
| 
 | 
 | ||||||
|     def options(self, request, *args, **kwargs): |     def options(self, request, *args, **kwargs): | ||||||
|         response_obj = { |         ret = { | ||||||
|             'name': get_name(self), |             'name': get_name(self), | ||||||
|             'description': get_description(self), |             'description': get_description(self), | ||||||
|             'renders': self._rendered_media_types, |             'renders': self._rendered_media_types, | ||||||
|  | @ -172,8 +174,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): | ||||||
|             field_name_types = {} |             field_name_types = {} | ||||||
|             for name, field in form.fields.iteritems(): |             for name, field in form.fields.iteritems(): | ||||||
|                 field_name_types[name] = field.__class__.__name__ |                 field_name_types[name] = field.__class__.__name__ | ||||||
|             response_obj['fields'] = field_name_types |             ret['fields'] = field_name_types | ||||||
|         return response_obj |         return ret | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ModelView(ModelMixin, View): | class ModelView(ModelMixin, View): | ||||||
|  | @ -191,7 +193,9 @@ class ModelView(ModelMixin, View): | ||||||
| 
 | 
 | ||||||
| class InstanceModelView(ModelView): | class InstanceModelView(ModelView): | ||||||
|     """ |     """ | ||||||
|     A view which provides default operations for read/update/delete against a model instance. |     A view which provides default operations for read/update/delete against a | ||||||
|  |     model instance.  This view is also treated as the Canonical identifier | ||||||
|  |     of the instances. | ||||||
|     """ |     """ | ||||||
|     _suffix = 'Instance' |     _suffix = 'Instance' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user