mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-30 21:44:04 +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