mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-28 04:24:00 +03:00
Strip trailing whitespace
Result of `find -name '*.py' -exec sed -i 's/[ \t]*$//' {} \;`
This commit is contained in:
parent
4f38e78580
commit
303a1108a7
|
@ -33,13 +33,13 @@ class BaseAuthentication(object):
|
||||||
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 can
|
||||||
handle the object and use it to determine if the request has the required
|
handle the object and use it to determine if the request has the required
|
||||||
permissions or not.
|
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 token
|
||||||
based authentication mechanism, where the authentication context
|
based authentication mechanism, where the authentication context
|
||||||
may be more involved than simply mapping to a :obj:`User`.
|
may be more involved than simply mapping to a :obj:`User`.
|
||||||
|
@ -55,10 +55,10 @@ class BasicAuthentication(BaseAuthentication):
|
||||||
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 supplied
|
||||||
using HTTP Basic authentication. Otherwise returns :const:`None`.
|
using HTTP Basic authentication. Otherwise returns :const:`None`.
|
||||||
"""
|
"""
|
||||||
from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
|
from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
|
||||||
|
|
||||||
if 'HTTP_AUTHORIZATION' in request.META:
|
if 'HTTP_AUTHORIZATION' in request.META:
|
||||||
auth = request.META['HTTP_AUTHORIZATION'].split()
|
auth = request.META['HTTP_AUTHORIZATION'].split()
|
||||||
if len(auth) == 2 and auth[0].lower() == "basic":
|
if len(auth) == 2 and auth[0].lower() == "basic":
|
||||||
|
@ -66,17 +66,17 @@ class BasicAuthentication(BaseAuthentication):
|
||||||
auth_parts = base64.b64decode(auth[1]).partition(':')
|
auth_parts = base64.b64decode(auth[1]).partition(':')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
uname, passwd = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2])
|
uname, passwd = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2])
|
||||||
except DjangoUnicodeDecodeError:
|
except DjangoUnicodeDecodeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
user = authenticate(username=uname, password=passwd)
|
user = authenticate(username=uname, password=passwd)
|
||||||
if user is not None and user.is_active:
|
if user is not None and user.is_active:
|
||||||
return user
|
return user
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class UserLoggedInAuthentication(BaseAuthentication):
|
class UserLoggedInAuthentication(BaseAuthentication):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -9,7 +9,7 @@ except ImportError:
|
||||||
import StringIO
|
import StringIO
|
||||||
|
|
||||||
|
|
||||||
# parse_qs
|
# parse_qs
|
||||||
try:
|
try:
|
||||||
# python >= ?
|
# python >= ?
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
|
@ -17,32 +17,32 @@ except ImportError:
|
||||||
# python <= ?
|
# python <= ?
|
||||||
from cgi import parse_qs
|
from cgi import parse_qs
|
||||||
|
|
||||||
|
|
||||||
# django.test.client.RequestFactory (Django >= 1.3)
|
# django.test.client.RequestFactory (Django >= 1.3)
|
||||||
try:
|
try:
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
|
|
||||||
# From: http://djangosnippets.org/snippets/963/
|
# From: http://djangosnippets.org/snippets/963/
|
||||||
# Lovely stuff
|
# Lovely stuff
|
||||||
class RequestFactory(Client):
|
class RequestFactory(Client):
|
||||||
"""
|
"""
|
||||||
Class that lets you create mock :obj:`Request` objects for use in testing.
|
Class that lets you create mock :obj:`Request` objects for use in testing.
|
||||||
|
|
||||||
Usage::
|
Usage::
|
||||||
|
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
get_request = rf.get('/hello/')
|
get_request = rf.get('/hello/')
|
||||||
post_request = rf.post('/submit/', {'foo': 'bar'})
|
post_request = rf.post('/submit/', {'foo': 'bar'})
|
||||||
|
|
||||||
This class re-uses the :class:`django.test.client.Client` interface. Of which
|
This class re-uses the :class:`django.test.client.Client` interface. Of which
|
||||||
you can find the docs here__.
|
you can find the docs here__.
|
||||||
|
|
||||||
__ http://www.djangoproject.com/documentation/testing/#the-test-client
|
__ http://www.djangoproject.com/documentation/testing/#the-test-client
|
||||||
|
|
||||||
Once you have a `request` object you can pass it to any :func:`view` function,
|
Once you have a `request` object you can pass it to any :func:`view` function,
|
||||||
just as if that :func:`view` had been hooked up using a URLconf.
|
just as if that :func:`view` had been hooked up using a URLconf.
|
||||||
"""
|
"""
|
||||||
def request(self, **request):
|
def request(self, **request):
|
||||||
|
@ -68,30 +68,30 @@ except ImportError:
|
||||||
try:
|
try:
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
if not hasattr(View, 'head'):
|
if not hasattr(View, 'head'):
|
||||||
# First implementation of Django class-based views did not include head method
|
# First implementation of Django class-based views did not include head method
|
||||||
# in base View class - https://code.djangoproject.com/ticket/15668
|
# in base View class - https://code.djangoproject.com/ticket/15668
|
||||||
class ViewPlusHead(View):
|
class ViewPlusHead(View):
|
||||||
def head(self, request, *args, **kwargs):
|
def head(self, request, *args, **kwargs):
|
||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
View = ViewPlusHead
|
View = ViewPlusHead
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from django import http
|
from django import http
|
||||||
from django.utils.functional import update_wrapper
|
from django.utils.functional import update_wrapper
|
||||||
# from django.utils.log import getLogger
|
# from django.utils.log import getLogger
|
||||||
# from django.utils.decorators import classonlymethod
|
# from django.utils.decorators import classonlymethod
|
||||||
|
|
||||||
# logger = getLogger('django.request') - We'll just drop support for logger if running Django <= 1.2
|
# logger = getLogger('django.request') - We'll just drop support for logger if running Django <= 1.2
|
||||||
# Might be nice to fix this up sometime to allow djangorestframework.compat.View to match 1.3's View more closely
|
# Might be nice to fix this up sometime to allow djangorestframework.compat.View to match 1.3's View more closely
|
||||||
|
|
||||||
class View(object):
|
class View(object):
|
||||||
"""
|
"""
|
||||||
Intentionally simple parent class for all views. Only implements
|
Intentionally simple parent class for all views. Only implements
|
||||||
dispatch-by-method and simple sanity checking.
|
dispatch-by-method and simple sanity checking.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
|
http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Constructor. Called in the URLconf; can contain helpful extra
|
Constructor. Called in the URLconf; can contain helpful extra
|
||||||
|
@ -101,7 +101,7 @@ except ImportError:
|
||||||
# instance, or raise an error.
|
# instance, or raise an error.
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in kwargs.iteritems():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
# @classonlymethod - We'll just us classmethod instead if running Django <= 1.2
|
# @classonlymethod - We'll just us classmethod instead if running Django <= 1.2
|
||||||
@classmethod
|
@classmethod
|
||||||
def as_view(cls, **initkwargs):
|
def as_view(cls, **initkwargs):
|
||||||
|
@ -117,19 +117,19 @@ except ImportError:
|
||||||
if not hasattr(cls, key):
|
if not hasattr(cls, key):
|
||||||
raise TypeError(u"%s() received an invalid keyword %r" % (
|
raise TypeError(u"%s() received an invalid keyword %r" % (
|
||||||
cls.__name__, key))
|
cls.__name__, key))
|
||||||
|
|
||||||
def view(request, *args, **kwargs):
|
def view(request, *args, **kwargs):
|
||||||
self = cls(**initkwargs)
|
self = cls(**initkwargs)
|
||||||
return self.dispatch(request, *args, **kwargs)
|
return self.dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
# take name and docstring from class
|
# take name and docstring from class
|
||||||
update_wrapper(view, cls, updated=())
|
update_wrapper(view, cls, updated=())
|
||||||
|
|
||||||
# and possible attributes set by decorators
|
# and possible attributes set by decorators
|
||||||
# like csrf_exempt from dispatch
|
# like csrf_exempt from dispatch
|
||||||
update_wrapper(view, cls.dispatch, assigned=())
|
update_wrapper(view, cls.dispatch, assigned=())
|
||||||
return view
|
return view
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
# Try to dispatch to the right method; if a method doesn't exist,
|
# Try to dispatch to the right method; if a method doesn't exist,
|
||||||
# defer to the error handler. Also defer to the error handler if the
|
# defer to the error handler. Also defer to the error handler if the
|
||||||
|
@ -142,7 +142,7 @@ except ImportError:
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
return handler(request, *args, **kwargs)
|
return handler(request, *args, **kwargs)
|
||||||
|
|
||||||
def http_method_not_allowed(self, request, *args, **kwargs):
|
def http_method_not_allowed(self, request, *args, **kwargs):
|
||||||
allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
|
allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
|
||||||
#logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
|
#logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
|
||||||
|
@ -160,20 +160,20 @@ except ImportError:
|
||||||
try:
|
try:
|
||||||
import markdown
|
import markdown
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
|
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
|
||||||
"""
|
"""
|
||||||
Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are <h2> and ---- headers are <h3>.
|
Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are <h2> and ---- headers are <h3>.
|
||||||
|
|
||||||
We use <h1> for the resource name.
|
We use <h1> for the resource name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Detect Setext-style header. Must be first 2 lines of block.
|
# Detect Setext-style header. Must be first 2 lines of block.
|
||||||
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
|
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
|
||||||
|
|
||||||
def test(self, parent, block):
|
def test(self, parent, block):
|
||||||
return bool(self.RE.match(block))
|
return bool(self.RE.match(block))
|
||||||
|
|
||||||
def run(self, parent, blocks):
|
def run(self, parent, blocks):
|
||||||
lines = blocks.pop(0).split('\n')
|
lines = blocks.pop(0).split('\n')
|
||||||
# Determine level. ``=`` is 1 and ``-`` is 2.
|
# Determine level. ``=`` is 1 and ``-`` is 2.
|
||||||
|
@ -186,19 +186,19 @@ try:
|
||||||
if len(lines) > 2:
|
if len(lines) > 2:
|
||||||
# Block contains additional lines. Add to master blocks for later.
|
# Block contains additional lines. Add to master blocks for later.
|
||||||
blocks.insert(0, '\n'.join(lines[2:]))
|
blocks.insert(0, '\n'.join(lines[2:]))
|
||||||
|
|
||||||
def apply_markdown(text):
|
def apply_markdown(text):
|
||||||
"""
|
"""
|
||||||
Simple wrapper around :func:`markdown.markdown` to apply our :class:`CustomSetextHeaderProcessor`,
|
Simple wrapper around :func:`markdown.markdown` to apply our :class:`CustomSetextHeaderProcessor`,
|
||||||
and also set the base level of '#' style headers to <h2>.
|
and also set the base level of '#' style headers to <h2>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
extensions = ['headerid(level=2)']
|
extensions = ['headerid(level=2)']
|
||||||
safe_mode = False,
|
safe_mode = False,
|
||||||
output_format = markdown.DEFAULT_OUTPUT_FORMAT
|
output_format = markdown.DEFAULT_OUTPUT_FORMAT
|
||||||
|
|
||||||
md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
|
md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
|
||||||
safe_mode=safe_mode,
|
safe_mode=safe_mode,
|
||||||
output_format=output_format)
|
output_format=output_format)
|
||||||
md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
|
md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
|
||||||
return md.convert(text)
|
return md.convert(text)
|
||||||
|
|
|
@ -49,15 +49,15 @@ class BaseParser(object):
|
||||||
in case the parser needs to access any metadata on the :obj:`View` object.
|
in case the parser needs to access any metadata on the :obj:`View` object.
|
||||||
"""
|
"""
|
||||||
self.view = view
|
self.view = view
|
||||||
|
|
||||||
def can_handle_request(self, content_type):
|
def can_handle_request(self, content_type):
|
||||||
"""
|
"""
|
||||||
Returns :const:`True` if this parser is able to deal with the given *content_type*.
|
Returns :const:`True` if this parser is able to deal with the given *content_type*.
|
||||||
|
|
||||||
The default implementation for this function is to check the *content_type*
|
The default implementation for this function is to check the *content_type*
|
||||||
argument against the :attr:`media_type` attribute set on the class to see if
|
argument against the :attr:`media_type` attribute set on the class to see if
|
||||||
they match.
|
they match.
|
||||||
|
|
||||||
This may be overridden to provide for other behavior, but typically you'll
|
This may be overridden to provide for other behavior, but typically you'll
|
||||||
instead want to just set the :attr:`media_type` attribute on the class.
|
instead want to just set the :attr:`media_type` attribute on the class.
|
||||||
"""
|
"""
|
||||||
|
@ -97,13 +97,13 @@ if yaml:
|
||||||
"""
|
"""
|
||||||
Parses YAML-serialized data.
|
Parses YAML-serialized data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
media_type = 'application/yaml'
|
media_type = 'application/yaml'
|
||||||
|
|
||||||
def parse(self, stream):
|
def parse(self, stream):
|
||||||
"""
|
"""
|
||||||
Returns a 2-tuple of `(data, files)`.
|
Returns a 2-tuple of `(data, files)`.
|
||||||
|
|
||||||
`data` will be an object which is the parsed content of the response.
|
`data` will be an object which is the parsed content of the response.
|
||||||
`files` will always be `None`.
|
`files` will always be `None`.
|
||||||
"""
|
"""
|
||||||
|
@ -125,7 +125,7 @@ class PlainTextParser(BaseParser):
|
||||||
def parse(self, stream):
|
def parse(self, stream):
|
||||||
"""
|
"""
|
||||||
Returns a 2-tuple of `(data, files)`.
|
Returns a 2-tuple of `(data, files)`.
|
||||||
|
|
||||||
`data` will simply be a string representing the body of the request.
|
`data` will simply be a string representing the body of the request.
|
||||||
`files` will always be `None`.
|
`files` will always be `None`.
|
||||||
"""
|
"""
|
||||||
|
@ -142,7 +142,7 @@ class FormParser(BaseParser):
|
||||||
def parse(self, stream):
|
def parse(self, stream):
|
||||||
"""
|
"""
|
||||||
Returns a 2-tuple of `(data, files)`.
|
Returns a 2-tuple of `(data, files)`.
|
||||||
|
|
||||||
`data` will be a :class:`QueryDict` containing all the form parameters.
|
`data` will be a :class:`QueryDict` containing all the form parameters.
|
||||||
`files` will always be :const:`None`.
|
`files` will always be :const:`None`.
|
||||||
"""
|
"""
|
||||||
|
@ -160,7 +160,7 @@ class MultiPartParser(BaseParser):
|
||||||
def parse(self, stream):
|
def parse(self, stream):
|
||||||
"""
|
"""
|
||||||
Returns a 2-tuple of `(data, files)`.
|
Returns a 2-tuple of `(data, files)`.
|
||||||
|
|
||||||
`data` will be a :class:`QueryDict` containing all the form parameters.
|
`data` will be a :class:`QueryDict` containing all the form parameters.
|
||||||
`files` will be a :class:`QueryDict` containing all the form files.
|
`files` will be a :class:`QueryDict` containing all the form files.
|
||||||
"""
|
"""
|
||||||
|
@ -171,8 +171,8 @@ class MultiPartParser(BaseParser):
|
||||||
raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
|
raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
|
||||||
{'detail': 'multipart parse error - %s' % unicode(exc)})
|
{'detail': 'multipart parse error - %s' % unicode(exc)})
|
||||||
return django_parser.parse()
|
return django_parser.parse()
|
||||||
|
|
||||||
|
|
||||||
class XMLParser(BaseParser):
|
class XMLParser(BaseParser):
|
||||||
"""
|
"""
|
||||||
XML parser.
|
XML parser.
|
||||||
|
@ -183,7 +183,7 @@ class XMLParser(BaseParser):
|
||||||
def parse(self, stream):
|
def parse(self, stream):
|
||||||
"""
|
"""
|
||||||
Returns a 2-tuple of `(data, files)`.
|
Returns a 2-tuple of `(data, files)`.
|
||||||
|
|
||||||
`data` will simply be a string representing the body of the request.
|
`data` will simply be a string representing the body of the request.
|
||||||
`files` will always be `None`.
|
`files` will always be `None`.
|
||||||
"""
|
"""
|
||||||
|
@ -191,32 +191,32 @@ class XMLParser(BaseParser):
|
||||||
tree = ET.parse(stream)
|
tree = ET.parse(stream)
|
||||||
for child in tree.getroot().getchildren():
|
for child in tree.getroot().getchildren():
|
||||||
data[child.tag] = self._type_convert(child.text)
|
data[child.tag] = self._type_convert(child.text)
|
||||||
|
|
||||||
return (data, None)
|
return (data, None)
|
||||||
|
|
||||||
def _type_convert(self, value):
|
def _type_convert(self, value):
|
||||||
"""
|
"""
|
||||||
Converts the value returned by the XMl parse into the equivalent
|
Converts the value returned by the XMl parse into the equivalent
|
||||||
Python type
|
Python type
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(value,'%Y-%m-%d %H:%M:%S')
|
return datetime.datetime.strptime(value,'%Y-%m-%d %H:%M:%S')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return decimal.Decimal(value)
|
return decimal.Decimal(value)
|
||||||
except decimal.InvalidOperation:
|
except decimal.InvalidOperation:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ Renderers are used to serialize a View's output into specific media types.
|
||||||
|
|
||||||
Django REST framework also provides HTML and PlainText renderers that help self-document the API,
|
Django REST framework also provides HTML and PlainText renderers that help self-document the API,
|
||||||
by serializing the output along with documentation regarding the View, output status and headers,
|
by serializing the output along with documentation regarding the View, output status and headers,
|
||||||
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
|
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
|
||||||
"""
|
"""
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -39,7 +39,7 @@ class BaseRenderer(object):
|
||||||
All renderers must extend this class, set the :attr:`media_type` attribute,
|
All renderers must extend this class, set the :attr:`media_type` attribute,
|
||||||
and override the :meth:`render` method.
|
and override the :meth:`render` method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_FORMAT_QUERY_PARAM = 'format'
|
_FORMAT_QUERY_PARAM = 'format'
|
||||||
|
|
||||||
media_type = None
|
media_type = None
|
||||||
|
@ -81,7 +81,7 @@ class BaseRenderer(object):
|
||||||
"""
|
"""
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,10 +135,10 @@ if yaml:
|
||||||
"""
|
"""
|
||||||
Renderer which serializes to YAML.
|
Renderer which serializes to YAML.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
media_type = 'application/yaml'
|
media_type = 'application/yaml'
|
||||||
format = 'yaml'
|
format = 'yaml'
|
||||||
|
|
||||||
def render(self, obj=None, media_type=None):
|
def render(self, obj=None, media_type=None):
|
||||||
"""
|
"""
|
||||||
Renders *obj* into serialized YAML.
|
Renders *obj* into serialized YAML.
|
||||||
|
@ -200,7 +200,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
content = renderers[0](view).render(obj, media_type)
|
content = renderers[0](view).render(obj, media_type)
|
||||||
if not all(char in string.printable for char in content):
|
if not all(char in string.printable for char in content):
|
||||||
return '[%d bytes of binary content]'
|
return '[%d bytes of binary content]'
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
# If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
|
# If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
|
||||||
if not form_instance:
|
if not form_instance:
|
||||||
form_instance = self._get_generic_content_form(view)
|
form_instance = self._get_generic_content_form(view)
|
||||||
|
|
||||||
return form_instance
|
return form_instance
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,7 +328,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
||||||
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
|
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
|
||||||
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX
|
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX
|
||||||
})
|
})
|
||||||
|
|
||||||
ret = template.render(context)
|
ret = template.render(context)
|
||||||
|
|
||||||
# Munge DELETE Response code to allow us to return content
|
# Munge DELETE Response code to allow us to return content
|
||||||
|
|
|
@ -34,7 +34,7 @@ class BaseResource(Serializer):
|
||||||
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||||
"""
|
"""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def filter_response(self, obj):
|
def filter_response(self, obj):
|
||||||
"""
|
"""
|
||||||
Given the response content, filter it into a serializable object.
|
Given the response content, filter it into a serializable object.
|
||||||
|
@ -47,11 +47,11 @@ class Resource(BaseResource):
|
||||||
A Resource determines how a python object maps to some serializable data.
|
A Resource determines how a python object maps to some serializable data.
|
||||||
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
|
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The model attribute refers to the Django Model which this Resource maps to.
|
# The model attribute refers to the Django Model which this Resource maps to.
|
||||||
# (The Model's class, rather than an instance of the Model)
|
# (The Model's class, rather than an instance of the Model)
|
||||||
model = None
|
model = None
|
||||||
|
|
||||||
# By default the set of returned fields will be the set of:
|
# By default the set of returned fields will be the set of:
|
||||||
#
|
#
|
||||||
# 0. All the fields on the model, excluding 'id'.
|
# 0. All the fields on the model, excluding 'id'.
|
||||||
|
@ -83,7 +83,7 @@ class FormResource(Resource):
|
||||||
"""
|
"""
|
||||||
Given some content as input return some cleaned, validated content.
|
Given some content as input return some cleaned, validated content.
|
||||||
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||||
|
|
||||||
Validation is standard form validation, with an additional constraint that *no extra unknown fields* may be supplied.
|
Validation is standard form validation, with an additional constraint that *no extra unknown fields* may be supplied.
|
||||||
|
|
||||||
On failure the :exc:`response.ErrorResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
On failure the :exc:`response.ErrorResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||||
|
@ -99,27 +99,27 @@ class FormResource(Resource):
|
||||||
|
|
||||||
allowed_extra_fields is a list of fields which are not defined by the form, but which we still
|
allowed_extra_fields is a list of fields which are not defined by the form, but which we still
|
||||||
expect to see on the input.
|
expect to see on the input.
|
||||||
|
|
||||||
fake_data is a string that should be used as an extra key, as a kludge to force .errors
|
fake_data is a string that should be used as an extra key, as a kludge to force .errors
|
||||||
to be populated when an empty dict is supplied in `data`
|
to be populated when an empty dict is supplied in `data`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We'd like nice error messages even if no content is supplied.
|
# We'd like nice error messages even if no content is supplied.
|
||||||
# Typically if an empty dict is given to a form Django will
|
# Typically if an empty dict is given to a form Django will
|
||||||
# return .is_valid() == False, but .errors == {}
|
# return .is_valid() == False, but .errors == {}
|
||||||
#
|
#
|
||||||
# To get around this case we revalidate with some fake data.
|
# To get around this case we revalidate with some fake data.
|
||||||
if fake_data:
|
if fake_data:
|
||||||
data[fake_data] = '_fake_data'
|
data[fake_data] = '_fake_data'
|
||||||
allowed_extra_fields = tuple(allowed_extra_fields) + ('_fake_data',)
|
allowed_extra_fields = tuple(allowed_extra_fields) + ('_fake_data',)
|
||||||
|
|
||||||
bound_form = self.get_bound_form(data, files)
|
bound_form = self.get_bound_form(data, files)
|
||||||
|
|
||||||
if bound_form is None:
|
if bound_form is None:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
self.view.bound_form_instance = bound_form
|
self.view.bound_form_instance = bound_form
|
||||||
|
|
||||||
data = data and data or {}
|
data = data and data or {}
|
||||||
files = files and files or {}
|
files = files and files or {}
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ class FormResource(Resource):
|
||||||
# In addition to regular validation we also ensure no additional fields are being passed in...
|
# In addition to regular validation we also ensure no additional fields are being passed in...
|
||||||
unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set)
|
unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set)
|
||||||
unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh.
|
unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh.
|
||||||
|
|
||||||
# Check using both regular validation, and our stricter no additional fields rule
|
# Check using both regular validation, and our stricter no additional fields rule
|
||||||
if bound_form.is_valid() and not unknown_fields:
|
if bound_form.is_valid() and not unknown_fields:
|
||||||
# Validation succeeded...
|
# Validation succeeded...
|
||||||
|
@ -155,7 +155,7 @@ class FormResource(Resource):
|
||||||
# If we've already set fake_dict and we're still here, fallback gracefully.
|
# If we've already set fake_dict and we're still here, fallback gracefully.
|
||||||
detail = {u'errors': [u'No content was supplied.']}
|
detail = {u'errors': [u'No content was supplied.']}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Add any non-field errors
|
# Add any non-field errors
|
||||||
if bound_form.non_field_errors():
|
if bound_form.non_field_errors():
|
||||||
detail[u'errors'] = bound_form.non_field_errors()
|
detail[u'errors'] = bound_form.non_field_errors()
|
||||||
|
@ -171,7 +171,7 @@ class FormResource(Resource):
|
||||||
# Add any unknown field errors
|
# Add any unknown field errors
|
||||||
for key in unknown_fields:
|
for key in unknown_fields:
|
||||||
field_errors[key] = [u'This field does not exist.']
|
field_errors[key] = [u'This field does not exist.']
|
||||||
|
|
||||||
if field_errors:
|
if field_errors:
|
||||||
detail[u'field-errors'] = field_errors
|
detail[u'field-errors'] = field_errors
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ class FormResource(Resource):
|
||||||
form = getattr(self.view, '%s_form' % method.lower(), form)
|
form = getattr(self.view, '%s_form' % method.lower(), form)
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
def get_bound_form(self, data=None, files=None, method=None):
|
def get_bound_form(self, data=None, files=None, method=None):
|
||||||
"""
|
"""
|
||||||
|
@ -237,7 +237,7 @@ class ModelResource(FormResource):
|
||||||
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
|
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Auto-register new ModelResource classes into _model_to_resource
|
# Auto-register new ModelResource classes into _model_to_resource
|
||||||
#__metaclass__ = _RegisterModelResource
|
#__metaclass__ = _RegisterModelResource
|
||||||
|
|
||||||
form = None
|
form = None
|
||||||
|
@ -258,21 +258,21 @@ class ModelResource(FormResource):
|
||||||
fields = None
|
fields = None
|
||||||
"""
|
"""
|
||||||
The list of fields to use on the output.
|
The list of fields to use on the output.
|
||||||
|
|
||||||
May be any of:
|
May be any of:
|
||||||
|
|
||||||
The name of a model field. To view nested resources, give the field as a tuple of ("fieldName", resource) where `resource` may be any of ModelResource reference, the name of a ModelResourc reference as a string or a tuple of strings representing fields on the nested model.
|
The name of a model field. To view nested resources, give the field as a tuple of ("fieldName", resource) where `resource` may be any of ModelResource reference, the name of a ModelResourc reference as a string or a tuple of strings representing fields on the nested model.
|
||||||
The name of an attribute on the model.
|
The name of an attribute on the model.
|
||||||
The name of an attribute on the resource.
|
The name of an attribute on the resource.
|
||||||
The name of a method on the model, with a signature like ``func(self)``.
|
The name of a method on the model, with a signature like ``func(self)``.
|
||||||
The name of a method on the resource, with a signature like ``func(self, instance)``.
|
The name of a method on the resource, with a signature like ``func(self, instance)``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
exclude = ('id', 'pk')
|
exclude = ('id', 'pk')
|
||||||
"""
|
"""
|
||||||
The list of fields to exclude. This is only used if :attr:`fields` is not set.
|
The list of fields to exclude. This is only used if :attr:`fields` is not set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
include = ('url',)
|
include = ('url',)
|
||||||
"""
|
"""
|
||||||
|
@ -294,7 +294,7 @@ class ModelResource(FormResource):
|
||||||
"""
|
"""
|
||||||
Given some content as input return some cleaned, validated content.
|
Given some content as input return some cleaned, validated content.
|
||||||
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||||
|
|
||||||
Validation is standard form or model form validation,
|
Validation is standard form or model form validation,
|
||||||
with an additional constraint that no extra unknown fields may be supplied,
|
with an additional constraint that no extra unknown fields may be supplied,
|
||||||
and that all fields specified by the fields class attribute must be supplied,
|
and that all fields specified by the fields class attribute must be supplied,
|
||||||
|
@ -345,12 +345,12 @@ class ModelResource(FormResource):
|
||||||
Attempts to reverse resolve the url of the given model *instance* for this resource.
|
Attempts to reverse resolve the url of the given model *instance* for this resource.
|
||||||
|
|
||||||
Requires a ``View`` with :class:`mixins.InstanceMixin` to have been created for this resource.
|
Requires a ``View`` with :class:`mixins.InstanceMixin` to have been created for this resource.
|
||||||
|
|
||||||
This method can be overridden if you need to set the resource url reversing explicitly.
|
This method can be overridden if you need to set the resource url reversing explicitly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not hasattr(self, 'view_callable'):
|
if not hasattr(self, 'view_callable'):
|
||||||
raise _SkipField
|
raise _SkipField
|
||||||
|
|
||||||
# dis does teh magicks...
|
# dis does teh magicks...
|
||||||
urlconf = get_urlconf()
|
urlconf = get_urlconf()
|
||||||
|
@ -393,7 +393,7 @@ class ModelResource(FormResource):
|
||||||
return model_fields & set(as_tuple(self.fields))
|
return model_fields & set(as_tuple(self.fields))
|
||||||
|
|
||||||
return model_fields - set(as_tuple(self.exclude))
|
return model_fields - set(as_tuple(self.exclude))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _property_fields_set(self):
|
def _property_fields_set(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""
|
"""
|
||||||
The :mod:`response` module provides Response classes you can use in your
|
The :mod:`response` module provides Response classes you can use in your
|
||||||
views to return a certain HTTP response. Typically a response is *rendered*
|
views to return a certain HTTP response. Typically a response is *rendered*
|
||||||
into a HTTP response depending on what renderers are set on your view and
|
into a HTTP response depending on what renderers are set on your view and
|
||||||
als depending on the accept header of the request.
|
als depending on the accept header of the request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||||
|
@ -23,7 +23,7 @@ class Response(object):
|
||||||
self.raw_content = content # content prior to filtering
|
self.raw_content = content # content prior to filtering
|
||||||
self.cleaned_content = content # content after filtering
|
self.cleaned_content = content # content after filtering
|
||||||
self.headers = headers or {}
|
self.headers = headers or {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status_text(self):
|
def status_text(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -51,10 +51,10 @@ def main():
|
||||||
|
|
||||||
# Drop the compat module from coverage, since we're not interested in the coverage
|
# Drop the compat module from coverage, since we're not interested in the coverage
|
||||||
# of a module which is specifically for resolving environment dependant imports.
|
# of a module which is specifically for resolving environment dependant imports.
|
||||||
# (Because we'll end up getting different coverage reports for it for each environment)
|
# (Because we'll end up getting different coverage reports for it for each environment)
|
||||||
if 'compat.py' in files:
|
if 'compat.py' in files:
|
||||||
files.remove('compat.py')
|
files.remove('compat.py')
|
||||||
|
|
||||||
cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')])
|
cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')])
|
||||||
|
|
||||||
cov.report(cov_files)
|
cov.report(cov_files)
|
||||||
|
|
|
@ -16,12 +16,12 @@ from django.test.utils import get_runner
|
||||||
def usage():
|
def usage():
|
||||||
return """
|
return """
|
||||||
Usage: python runtests.py [UnitTestClass].[method]
|
Usage: python runtests.py [UnitTestClass].[method]
|
||||||
|
|
||||||
You can pass the Class name of the `UnitTestClass` you want to test.
|
You can pass the Class name of the `UnitTestClass` you want to test.
|
||||||
|
|
||||||
Append a method name if you only want to test a specific method of that class.
|
Append a method name if you only want to test a specific method of that class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
TestRunner = get_runner(settings)
|
TestRunner = get_runner(settings)
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ MEDIA_URL = ''
|
||||||
|
|
||||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||||
# trailing slash.
|
# trailing slash.
|
||||||
# Examples: "http://foo.com/media/", "/media/".
|
# Examples: "http://foo.com/media/", "/media/".
|
||||||
ADMIN_MEDIA_PREFIX = '/media/'
|
ADMIN_MEDIA_PREFIX = '/media/'
|
||||||
|
|
||||||
# Make this unique, and don't share it with anybody.
|
# Make this unique, and don't share it with anybody.
|
||||||
|
|
|
@ -18,7 +18,7 @@ _serializers = {}
|
||||||
|
|
||||||
def _field_to_tuple(field):
|
def _field_to_tuple(field):
|
||||||
"""
|
"""
|
||||||
Convert an item in the `fields` attribute into a 2-tuple.
|
Convert an item in the `fields` attribute into a 2-tuple.
|
||||||
"""
|
"""
|
||||||
if isinstance(field, (tuple, list)):
|
if isinstance(field, (tuple, list)):
|
||||||
return (field[0], field[1])
|
return (field[0], field[1])
|
||||||
|
@ -52,7 +52,7 @@ class _RegisterSerializer(type):
|
||||||
"""
|
"""
|
||||||
def __new__(cls, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
# Build the class and register it.
|
# Build the class and register it.
|
||||||
ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs)
|
ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs)
|
||||||
_serializers[name] = ret
|
_serializers[name] = ret
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -61,19 +61,19 @@ class Serializer(object):
|
||||||
"""
|
"""
|
||||||
Converts python objects into plain old native types suitable for
|
Converts python objects into plain old native types suitable for
|
||||||
serialization. In particular it handles models and querysets.
|
serialization. In particular it handles models and querysets.
|
||||||
|
|
||||||
The output format is specified by setting a number of attributes
|
The output format is specified by setting a number of attributes
|
||||||
on the class.
|
on the class.
|
||||||
|
|
||||||
You may also override any of the serialization methods, to provide
|
You may also override any of the serialization methods, to provide
|
||||||
for more flexible behavior.
|
for more flexible behavior.
|
||||||
|
|
||||||
Valid output types include anything that may be directly rendered into
|
Valid output types include anything that may be directly rendered into
|
||||||
json, xml etc...
|
json, xml etc...
|
||||||
"""
|
"""
|
||||||
__metaclass__ = _RegisterSerializer
|
__metaclass__ = _RegisterSerializer
|
||||||
|
|
||||||
fields = ()
|
fields = ()
|
||||||
"""
|
"""
|
||||||
Specify the fields to be serialized on a model or dict.
|
Specify the fields to be serialized on a model or dict.
|
||||||
Overrides `include` and `exclude`.
|
Overrides `include` and `exclude`.
|
||||||
|
@ -109,7 +109,7 @@ class Serializer(object):
|
||||||
if depth is not None:
|
if depth is not None:
|
||||||
self.depth = depth
|
self.depth = depth
|
||||||
self.stack = stack
|
self.stack = stack
|
||||||
|
|
||||||
|
|
||||||
def get_fields(self, obj):
|
def get_fields(self, obj):
|
||||||
"""
|
"""
|
||||||
|
@ -168,7 +168,7 @@ class Serializer(object):
|
||||||
# Similar to what Django does for cyclically related models.
|
# Similar to what Django does for cyclically related models.
|
||||||
elif isinstance(info, str) and info in _serializers:
|
elif isinstance(info, str) and info in _serializers:
|
||||||
return _serializers[info]
|
return _serializers[info]
|
||||||
|
|
||||||
# Otherwise use `related_serializer` or fall back to `Serializer`
|
# Otherwise use `related_serializer` or fall back to `Serializer`
|
||||||
return getattr(self, 'related_serializer') or Serializer
|
return getattr(self, 'related_serializer') or Serializer
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ class Serializer(object):
|
||||||
Convert a model field or dict value into a serializable representation.
|
Convert a model field or dict value into a serializable representation.
|
||||||
"""
|
"""
|
||||||
related_serializer = self.get_related_serializer(key)
|
related_serializer = self.get_related_serializer(key)
|
||||||
|
|
||||||
if self.depth is None:
|
if self.depth is None:
|
||||||
depth = None
|
depth = None
|
||||||
elif self.depth <= 0:
|
elif self.depth <= 0:
|
||||||
|
@ -227,7 +227,7 @@ class Serializer(object):
|
||||||
|
|
||||||
fields = self.get_fields(instance)
|
fields = self.get_fields(instance)
|
||||||
|
|
||||||
# serialize each required field
|
# serialize each required field
|
||||||
for fname in fields:
|
for fname in fields:
|
||||||
try:
|
try:
|
||||||
if hasattr(self, smart_str(fname)):
|
if hasattr(self, smart_str(fname)):
|
||||||
|
@ -279,13 +279,13 @@ class Serializer(object):
|
||||||
Convert any unhandled object into a serializable representation.
|
Convert any unhandled object into a serializable representation.
|
||||||
"""
|
"""
|
||||||
return smart_unicode(obj, strings_only=True)
|
return smart_unicode(obj, strings_only=True)
|
||||||
|
|
||||||
|
|
||||||
def serialize(self, obj):
|
def serialize(self, obj):
|
||||||
"""
|
"""
|
||||||
Convert any object into a serializable representation.
|
Convert any object into a serializable representation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(obj, (dict, models.Model)):
|
if isinstance(obj, (dict, models.Model)):
|
||||||
# Model instances & dictionaries
|
# Model instances & dictionaries
|
||||||
return self.serialize_model(obj)
|
return self.serialize_model(obj)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Adds the custom filter 'urlize_quoted_links'
|
"""Adds the custom filter 'urlize_quoted_links'
|
||||||
|
|
||||||
This is identical to the built-in filter 'urlize' with the exception that
|
This is identical to the built-in filter 'urlize' with the exception that
|
||||||
single and double quotes are permitted as leading or trailing punctuation.
|
single and double quotes are permitted as leading or trailing punctuation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class UserAgentMungingTest(TestCase):
|
||||||
req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
|
req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
|
||||||
resp = view(req)
|
resp = view(req)
|
||||||
self.assertEqual(resp['Content-Type'], 'application/json')
|
self.assertEqual(resp['Content-Type'], 'application/json')
|
||||||
|
|
||||||
def test_dont_munge_nice_browsers_accept_header(self):
|
def test_dont_munge_nice_browsers_accept_header(self):
|
||||||
"""Send Non-MSIE user agent strings and ensure that we get a JSON response,
|
"""Send Non-MSIE user agent strings and ensure that we get a JSON response,
|
||||||
if we set a */* Accept header. (Other browsers will correctly set the Accept header)"""
|
if we set a */* Accept header. (Other browsers will correctly set the Accept header)"""
|
||||||
|
|
|
@ -31,7 +31,7 @@ class BasicAuthTests(TestCase):
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
self.user = User.objects.create_user(self.username, self.email, self.password)
|
self.user = User.objects.create_user(self.username, self.email, self.password)
|
||||||
|
|
||||||
def test_post_form_passing_basic_auth(self):
|
def test_post_form_passing_basic_auth(self):
|
||||||
"""Ensure POSTing json over basic auth with correct credentials passes and does not require CSRF"""
|
"""Ensure POSTing json over basic auth with correct credentials passes and does not require CSRF"""
|
||||||
|
@ -66,7 +66,7 @@ class SessionAuthTests(TestCase):
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
self.user = User.objects.create_user(self.username, self.email, self.password)
|
self.user = User.objects.create_user(self.username, self.email, self.password)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.csrf_client.logout()
|
self.csrf_client.logout()
|
||||||
|
|
|
@ -55,16 +55,16 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
|
|
||||||
* list
|
* list
|
||||||
* list
|
* list
|
||||||
|
|
||||||
another header
|
another header
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
code block
|
code block
|
||||||
|
|
||||||
indented
|
indented
|
||||||
|
|
||||||
# hash style header #"""
|
# hash style header #"""
|
||||||
|
|
||||||
self.assertEquals(get_description(MockView()), DESCRIPTION)
|
self.assertEquals(get_description(MockView()), DESCRIPTION)
|
||||||
|
|
||||||
# This has been turned off now
|
# This has been turned off now
|
||||||
|
@ -75,7 +75,7 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
# """docstring"""
|
# """docstring"""
|
||||||
# description = example
|
# description = example
|
||||||
# self.assertEquals(get_description(MockView()), example)
|
# self.assertEquals(get_description(MockView()), example)
|
||||||
|
|
||||||
#def test_resource_description_does_not_require_docstring(self):
|
#def test_resource_description_does_not_require_docstring(self):
|
||||||
# """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute."""
|
# """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute."""
|
||||||
# example = 'Some other description'
|
# example = 'Some other description'
|
||||||
|
@ -88,7 +88,7 @@ class TestViewNamesAndDescriptions(TestCase):
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
pass
|
pass
|
||||||
self.assertEquals(get_description(MockView()), '')
|
self.assertEquals(get_description(MockView()), '')
|
||||||
|
|
||||||
def test_markdown(self):
|
def test_markdown(self):
|
||||||
"""Ensure markdown to HTML works as expected"""
|
"""Ensure markdown to HTML works as expected"""
|
||||||
if apply_markdown:
|
if apply_markdown:
|
||||||
|
|
|
@ -22,7 +22,7 @@ class UploadFilesTests(TestCase):
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
return {'FILE_NAME': self.CONTENT['file'].name,
|
return {'FILE_NAME': self.CONTENT['file'].name,
|
||||||
'FILE_CONTENT': self.CONTENT['file'].read()}
|
'FILE_CONTENT': self.CONTENT['file'].read()}
|
||||||
|
|
||||||
file = StringIO.StringIO('stuff')
|
file = StringIO.StringIO('stuff')
|
||||||
file.name = 'stuff.txt'
|
file.name = 'stuff.txt'
|
||||||
request = self.factory.post('/', {'file': file})
|
request = self.factory.post('/', {'file': file})
|
||||||
|
|
|
@ -3,7 +3,7 @@ from djangorestframework.compat import RequestFactory
|
||||||
from djangorestframework.mixins import RequestMixin
|
from djangorestframework.mixins import RequestMixin
|
||||||
|
|
||||||
|
|
||||||
class TestMethodOverloading(TestCase):
|
class TestMethodOverloading(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.req = RequestFactory()
|
self.req = RequestFactory()
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class TestMethodOverloading(TestCase):
|
||||||
view = RequestMixin()
|
view = RequestMixin()
|
||||||
view.request = self.req.post('/')
|
view.request = self.req.post('/')
|
||||||
self.assertEqual(view.method, 'POST')
|
self.assertEqual(view.method, 'POST')
|
||||||
|
|
||||||
def test_overloaded_POST_behaviour_determines_overloaded_method(self):
|
def test_overloaded_POST_behaviour_determines_overloaded_method(self):
|
||||||
"""POST requests can be overloaded to another method by setting a reserved form field"""
|
"""POST requests can be overloaded to another method by setting a reserved form field"""
|
||||||
view = RequestMixin()
|
view = RequestMixin()
|
||||||
|
|
|
@ -25,4 +25,4 @@ class UserGroupMap(models.Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return ('user_group_map', (), {
|
return ('user_group_map', (), {
|
||||||
'pk': self.id
|
'pk': self.id
|
||||||
})
|
})
|
|
@ -17,9 +17,9 @@ class UserForm(ModelForm):
|
||||||
class UserResource(ModelResource):
|
class UserResource(ModelResource):
|
||||||
model = User
|
model = User
|
||||||
form = UserForm
|
form = UserForm
|
||||||
|
|
||||||
class CustomUserResource(ModelResource):
|
class CustomUserResource(ModelResource):
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
|
url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
|
||||||
|
@ -33,7 +33,7 @@ urlpatterns = patterns('',
|
||||||
|
|
||||||
class ModelViewTests(TestCase):
|
class ModelViewTests(TestCase):
|
||||||
"""Test the model views djangorestframework provides"""
|
"""Test the model views djangorestframework provides"""
|
||||||
urls = 'djangorestframework.tests.modelviews'
|
urls = 'djangorestframework.tests.modelviews'
|
||||||
|
|
||||||
def test_creation(self):
|
def test_creation(self):
|
||||||
"""Ensure that a model object can be created"""
|
"""Ensure that a model object can be created"""
|
||||||
|
@ -52,18 +52,18 @@ class ModelViewTests(TestCase):
|
||||||
self.assertEqual(0, User.objects.count())
|
self.assertEqual(0, User.objects.count())
|
||||||
|
|
||||||
response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
|
response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(1, User.objects.count())
|
self.assertEqual(1, User.objects.count())
|
||||||
|
|
||||||
user = User.objects.all()[0]
|
user = User.objects.all()[0]
|
||||||
self.assertEqual('bar', user.username)
|
self.assertEqual('bar', user.username)
|
||||||
self.assertEqual('baz', user.password)
|
self.assertEqual('baz', user.password)
|
||||||
self.assertEqual(1, user.groups.count())
|
self.assertEqual(1, user.groups.count())
|
||||||
|
|
||||||
group = user.groups.all()[0]
|
group = user.groups.all()[0]
|
||||||
self.assertEqual('foo', group.name)
|
self.assertEqual('foo', group.name)
|
||||||
|
|
||||||
def test_creation_with_m2m_relation_through(self):
|
def test_creation_with_m2m_relation_through(self):
|
||||||
"""
|
"""
|
||||||
Ensure that a model object with a m2m relation can be created where that
|
Ensure that a model object with a m2m relation can be created where that
|
||||||
|
@ -74,13 +74,13 @@ class ModelViewTests(TestCase):
|
||||||
self.assertEqual(0, User.objects.count())
|
self.assertEqual(0, User.objects.count())
|
||||||
|
|
||||||
response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
|
response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertEqual(1, CustomUser.objects.count())
|
self.assertEqual(1, CustomUser.objects.count())
|
||||||
|
|
||||||
user = CustomUser.objects.all()[0]
|
user = CustomUser.objects.all()[0]
|
||||||
self.assertEqual('bar', user.username)
|
self.assertEqual('bar', user.username)
|
||||||
self.assertEqual(1, user.groups.count())
|
self.assertEqual(1, user.groups.count())
|
||||||
|
|
||||||
group = user.groups.all()[0]
|
group = user.groups.all()[0]
|
||||||
self.assertEqual('foo', group.name)
|
self.assertEqual('foo', group.name)
|
||||||
|
|
|
@ -23,14 +23,14 @@ else:
|
||||||
class ClientView(View):
|
class ClientView(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return {'resource': 'Protected!'}
|
return {'resource': 'Protected!'}
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', oauth_required(ClientView.as_view())),
|
url(r'^$', oauth_required(ClientView.as_view())),
|
||||||
url(r'^oauth/', include('oauth_provider.urls')),
|
url(r'^oauth/', include('oauth_provider.urls')),
|
||||||
url(r'^accounts/login/$', 'djangorestframework.utils.staticviews.api_login'),
|
url(r'^accounts/login/$', 'djangorestframework.utils.staticviews.api_login'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OAuthTests(TestCase):
|
class OAuthTests(TestCase):
|
||||||
"""
|
"""
|
||||||
OAuth authentication:
|
OAuth authentication:
|
||||||
|
@ -42,23 +42,23 @@ else:
|
||||||
* the third-party website is able to retrieve data from the API
|
* the third-party website is able to retrieve data from the API
|
||||||
"""
|
"""
|
||||||
urls = 'djangorestframework.tests.oauthentication'
|
urls = 'djangorestframework.tests.oauthentication'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
self.user = User.objects.create_user(self.username, self.email, self.password)
|
self.user = User.objects.create_user(self.username, self.email, self.password)
|
||||||
|
|
||||||
# OAuth requirements
|
# OAuth requirements
|
||||||
self.resource = Resource(name='data', url='/')
|
self.resource = Resource(name='data', url='/')
|
||||||
self.resource.save()
|
self.resource.save()
|
||||||
self.CONSUMER_KEY = 'dpf43f3p2l4k3l03'
|
self.CONSUMER_KEY = 'dpf43f3p2l4k3l03'
|
||||||
self.CONSUMER_SECRET = 'kd94hf93k423kf44'
|
self.CONSUMER_SECRET = 'kd94hf93k423kf44'
|
||||||
self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET,
|
self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET,
|
||||||
name='api.example.com', user=self.user)
|
name='api.example.com', user=self.user)
|
||||||
self.consumer.save()
|
self.consumer.save()
|
||||||
|
|
||||||
def test_oauth_invalid_and_anonymous_access(self):
|
def test_oauth_invalid_and_anonymous_access(self):
|
||||||
"""
|
"""
|
||||||
Verify that the resource is protected and the OAuth authorization view
|
Verify that the resource is protected and the OAuth authorization view
|
||||||
|
@ -69,16 +69,16 @@ else:
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
response = self.client.get('/oauth/authorize/', follow=True)
|
response = self.client.get('/oauth/authorize/', follow=True)
|
||||||
self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/')
|
self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/')
|
||||||
|
|
||||||
def test_oauth_authorize_access(self):
|
def test_oauth_authorize_access(self):
|
||||||
"""
|
"""
|
||||||
Verify that once logged in, the user can access the authorization page
|
Verify that once logged in, the user can access the authorization page
|
||||||
but can't display the page because the request token is not specified.
|
but can't display the page because the request token is not specified.
|
||||||
"""
|
"""
|
||||||
self.client.login(username=self.username, password=self.password)
|
self.client.login(username=self.username, password=self.password)
|
||||||
response = self.client.get('/oauth/authorize/', follow=True)
|
response = self.client.get('/oauth/authorize/', follow=True)
|
||||||
self.assertEqual(response.content, 'No request token specified.')
|
self.assertEqual(response.content, 'No request token specified.')
|
||||||
|
|
||||||
def _create_request_token_parameters(self):
|
def _create_request_token_parameters(self):
|
||||||
"""
|
"""
|
||||||
A shortcut to create request's token parameters.
|
A shortcut to create request's token parameters.
|
||||||
|
@ -93,28 +93,28 @@ else:
|
||||||
'oauth_callback': 'http://api.example.com/request_token_ready',
|
'oauth_callback': 'http://api.example.com/request_token_ready',
|
||||||
'scope': 'data',
|
'scope': 'data',
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_oauth_request_token_retrieval(self):
|
def test_oauth_request_token_retrieval(self):
|
||||||
"""
|
"""
|
||||||
Verify that the request token can be retrieved by the server.
|
Verify that the request token can be retrieved by the server.
|
||||||
"""
|
"""
|
||||||
response = self.client.get("/oauth/request_token/",
|
response = self.client.get("/oauth/request_token/",
|
||||||
self._create_request_token_parameters())
|
self._create_request_token_parameters())
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
token = list(Token.objects.all())[-1]
|
token = list(Token.objects.all())[-1]
|
||||||
self.failIf(token.key not in response.content)
|
self.failIf(token.key not in response.content)
|
||||||
self.failIf(token.secret not in response.content)
|
self.failIf(token.secret not in response.content)
|
||||||
|
|
||||||
def test_oauth_user_request_authorization(self):
|
def test_oauth_user_request_authorization(self):
|
||||||
"""
|
"""
|
||||||
Verify that the user can access the authorization page once logged in
|
Verify that the user can access the authorization page once logged in
|
||||||
and the request token has been retrieved.
|
and the request token has been retrieved.
|
||||||
"""
|
"""
|
||||||
# Setup
|
# Setup
|
||||||
response = self.client.get("/oauth/request_token/",
|
response = self.client.get("/oauth/request_token/",
|
||||||
self._create_request_token_parameters())
|
self._create_request_token_parameters())
|
||||||
token = list(Token.objects.all())[-1]
|
token = list(Token.objects.all())[-1]
|
||||||
|
|
||||||
# Starting the test here
|
# Starting the test here
|
||||||
self.client.login(username=self.username, password=self.password)
|
self.client.login(username=self.username, password=self.password)
|
||||||
parameters = {'oauth_token': token.key,}
|
parameters = {'oauth_token': token.key,}
|
||||||
|
@ -129,7 +129,7 @@ else:
|
||||||
token = Token.objects.get(key=token.key)
|
token = Token.objects.get(key=token.key)
|
||||||
self.failIf(token.key not in response['Location'])
|
self.failIf(token.key not in response['Location'])
|
||||||
self.assertEqual(token.is_approved, 1)
|
self.assertEqual(token.is_approved, 1)
|
||||||
|
|
||||||
def _create_access_token_parameters(self, token):
|
def _create_access_token_parameters(self, token):
|
||||||
"""
|
"""
|
||||||
A shortcut to create access' token parameters.
|
A shortcut to create access' token parameters.
|
||||||
|
@ -145,13 +145,13 @@ else:
|
||||||
'oauth_verifier': token.verifier,
|
'oauth_verifier': token.verifier,
|
||||||
'scope': 'data',
|
'scope': 'data',
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_oauth_access_token_retrieval(self):
|
def test_oauth_access_token_retrieval(self):
|
||||||
"""
|
"""
|
||||||
Verify that the request token can be retrieved by the server.
|
Verify that the request token can be retrieved by the server.
|
||||||
"""
|
"""
|
||||||
# Setup
|
# Setup
|
||||||
response = self.client.get("/oauth/request_token/",
|
response = self.client.get("/oauth/request_token/",
|
||||||
self._create_request_token_parameters())
|
self._create_request_token_parameters())
|
||||||
token = list(Token.objects.all())[-1]
|
token = list(Token.objects.all())[-1]
|
||||||
self.client.login(username=self.username, password=self.password)
|
self.client.login(username=self.username, password=self.password)
|
||||||
|
@ -160,7 +160,7 @@ else:
|
||||||
parameters['authorize_access'] = 1 # fake authorization by the user
|
parameters['authorize_access'] = 1 # fake authorization by the user
|
||||||
response = self.client.post("/oauth/authorize/", parameters)
|
response = self.client.post("/oauth/authorize/", parameters)
|
||||||
token = Token.objects.get(key=token.key)
|
token = Token.objects.get(key=token.key)
|
||||||
|
|
||||||
# Starting the test here
|
# Starting the test here
|
||||||
response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
|
response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -169,7 +169,7 @@ else:
|
||||||
self.failIf(access_token.key not in response.content)
|
self.failIf(access_token.key not in response.content)
|
||||||
self.failIf(access_token.secret not in response.content)
|
self.failIf(access_token.secret not in response.content)
|
||||||
self.assertEqual(access_token.user.username, 'john')
|
self.assertEqual(access_token.user.username, 'john')
|
||||||
|
|
||||||
def _create_access_parameters(self, access_token):
|
def _create_access_parameters(self, access_token):
|
||||||
"""
|
"""
|
||||||
A shortcut to create access' parameters.
|
A shortcut to create access' parameters.
|
||||||
|
@ -188,13 +188,13 @@ else:
|
||||||
signature = signature_method.sign(oauth_request, self.consumer, access_token)
|
signature = signature_method.sign(oauth_request, self.consumer, access_token)
|
||||||
parameters['oauth_signature'] = signature
|
parameters['oauth_signature'] = signature
|
||||||
return parameters
|
return parameters
|
||||||
|
|
||||||
def test_oauth_protected_resource_access(self):
|
def test_oauth_protected_resource_access(self):
|
||||||
"""
|
"""
|
||||||
Verify that the request token can be retrieved by the server.
|
Verify that the request token can be retrieved by the server.
|
||||||
"""
|
"""
|
||||||
# Setup
|
# Setup
|
||||||
response = self.client.get("/oauth/request_token/",
|
response = self.client.get("/oauth/request_token/",
|
||||||
self._create_request_token_parameters())
|
self._create_request_token_parameters())
|
||||||
token = list(Token.objects.all())[-1]
|
token = list(Token.objects.all())[-1]
|
||||||
self.client.login(username=self.username, password=self.password)
|
self.client.login(username=self.username, password=self.password)
|
||||||
|
@ -205,7 +205,7 @@ else:
|
||||||
token = Token.objects.get(key=token.key)
|
token = Token.objects.get(key=token.key)
|
||||||
response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
|
response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
|
||||||
access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
|
access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
|
||||||
|
|
||||||
# Starting the test here
|
# Starting the test here
|
||||||
response = self.client.get("/", self._create_access_token_parameters(access_token))
|
response = self.client.get("/", self._create_access_token_parameters(access_token))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
import djangorestframework
|
import djangorestframework
|
||||||
|
|
||||||
class TestVersion(TestCase):
|
class TestVersion(TestCase):
|
||||||
"""Simple sanity test to check the VERSION exists"""
|
"""Simple sanity test to check the VERSION exists"""
|
||||||
|
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
|
|
|
@ -8,76 +8,76 @@
|
||||||
# >>> req = RequestFactory().get('/')
|
# >>> req = RequestFactory().get('/')
|
||||||
# >>> some_view = View()
|
# >>> some_view = View()
|
||||||
# >>> some_view.request = req # Make as if this request had been dispatched
|
# >>> some_view.request = req # Make as if this request had been dispatched
|
||||||
#
|
#
|
||||||
# FormParser
|
# FormParser
|
||||||
# ============
|
# ============
|
||||||
#
|
#
|
||||||
# Data flatening
|
# Data flatening
|
||||||
# ----------------
|
# ----------------
|
||||||
#
|
#
|
||||||
# Here is some example data, which would eventually be sent along with a post request :
|
# Here is some example data, which would eventually be sent along with a post request :
|
||||||
#
|
#
|
||||||
# >>> inpt = urlencode([
|
# >>> inpt = urlencode([
|
||||||
# ... ('key1', 'bla1'),
|
# ... ('key1', 'bla1'),
|
||||||
# ... ('key2', 'blo1'), ('key2', 'blo2'),
|
# ... ('key2', 'blo1'), ('key2', 'blo2'),
|
||||||
# ... ])
|
# ... ])
|
||||||
#
|
#
|
||||||
# Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter :
|
# Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter :
|
||||||
#
|
#
|
||||||
# >>> (data, files) = FormParser(some_view).parse(StringIO(inpt))
|
# >>> (data, files) = FormParser(some_view).parse(StringIO(inpt))
|
||||||
# >>> data == {'key1': 'bla1', 'key2': 'blo1'}
|
# >>> data == {'key1': 'bla1', 'key2': 'blo1'}
|
||||||
# True
|
# True
|
||||||
#
|
#
|
||||||
# However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` :
|
# However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` :
|
||||||
#
|
#
|
||||||
# >>> class MyFormParser(FormParser):
|
# >>> class MyFormParser(FormParser):
|
||||||
# ...
|
# ...
|
||||||
# ... def is_a_list(self, key, val_list):
|
# ... def is_a_list(self, key, val_list):
|
||||||
# ... return len(val_list) > 1
|
# ... return len(val_list) > 1
|
||||||
#
|
#
|
||||||
# This new parser only flattens the lists of parameters that contain a single value.
|
# This new parser only flattens the lists of parameters that contain a single value.
|
||||||
#
|
#
|
||||||
# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
|
# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
|
||||||
# >>> data == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
|
# >>> data == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
|
||||||
# True
|
# True
|
||||||
#
|
#
|
||||||
# .. note:: The same functionality is available for :class:`parsers.MultiPartParser`.
|
# .. note:: The same functionality is available for :class:`parsers.MultiPartParser`.
|
||||||
#
|
#
|
||||||
# Submitting an empty list
|
# Submitting an empty list
|
||||||
# --------------------------
|
# --------------------------
|
||||||
#
|
#
|
||||||
# When submitting an empty select multiple, like this one ::
|
# When submitting an empty select multiple, like this one ::
|
||||||
#
|
#
|
||||||
# <select multiple="multiple" name="key2"></select>
|
# <select multiple="multiple" name="key2"></select>
|
||||||
#
|
#
|
||||||
# The browsers usually strip the parameter completely. A hack to avoid this, and therefore being able to submit an empty select multiple, is to submit a value that tells the server that the list is empty ::
|
# The browsers usually strip the parameter completely. A hack to avoid this, and therefore being able to submit an empty select multiple, is to submit a value that tells the server that the list is empty ::
|
||||||
#
|
#
|
||||||
# <select multiple="multiple" name="key2"><option value="_empty"></select>
|
# <select multiple="multiple" name="key2"><option value="_empty"></select>
|
||||||
#
|
#
|
||||||
# :class:`parsers.FormParser` provides the server-side implementation for this hack. Considering the following posted data :
|
# :class:`parsers.FormParser` provides the server-side implementation for this hack. Considering the following posted data :
|
||||||
#
|
#
|
||||||
# >>> inpt = urlencode([
|
# >>> inpt = urlencode([
|
||||||
# ... ('key1', 'blo1'), ('key1', '_empty'),
|
# ... ('key1', 'blo1'), ('key1', '_empty'),
|
||||||
# ... ('key2', '_empty'),
|
# ... ('key2', '_empty'),
|
||||||
# ... ])
|
# ... ])
|
||||||
#
|
#
|
||||||
# :class:`parsers.FormParser` strips the values ``_empty`` from all the lists.
|
# :class:`parsers.FormParser` strips the values ``_empty`` from all the lists.
|
||||||
#
|
#
|
||||||
# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
|
# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
|
||||||
# >>> data == {'key1': 'blo1'}
|
# >>> data == {'key1': 'blo1'}
|
||||||
# True
|
# True
|
||||||
#
|
#
|
||||||
# Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it.
|
# Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it.
|
||||||
#
|
#
|
||||||
# >>> class MyFormParser(FormParser):
|
# >>> class MyFormParser(FormParser):
|
||||||
# ...
|
# ...
|
||||||
# ... def is_a_list(self, key, val_list):
|
# ... def is_a_list(self, key, val_list):
|
||||||
# ... return key == 'key2'
|
# ... return key == 'key2'
|
||||||
# ...
|
# ...
|
||||||
# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
|
# >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
|
||||||
# >>> data == {'key1': 'blo1', 'key2': []}
|
# >>> data == {'key1': 'blo1', 'key2': []}
|
||||||
# True
|
# True
|
||||||
#
|
#
|
||||||
# Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`.
|
# Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`.
|
||||||
# """
|
# """
|
||||||
# import httplib, mimetypes
|
# import httplib, mimetypes
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
# from djangorestframework.parsers import MultiPartParser
|
# from djangorestframework.parsers import MultiPartParser
|
||||||
# from djangorestframework.views import View
|
# from djangorestframework.views import View
|
||||||
# from StringIO import StringIO
|
# from StringIO import StringIO
|
||||||
#
|
#
|
||||||
# def encode_multipart_formdata(fields, files):
|
# def encode_multipart_formdata(fields, files):
|
||||||
# """For testing multipart parser.
|
# """For testing multipart parser.
|
||||||
# fields is a sequence of (name, value) elements for regular form fields.
|
# fields is a sequence of (name, value) elements for regular form fields.
|
||||||
|
@ -112,10 +112,10 @@
|
||||||
# body = CRLF.join(L)
|
# body = CRLF.join(L)
|
||||||
# content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
|
# content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
|
||||||
# return content_type, body
|
# return content_type, body
|
||||||
#
|
#
|
||||||
# def get_content_type(filename):
|
# def get_content_type(filename):
|
||||||
# return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
# return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||||
#
|
#
|
||||||
#class TestMultiPartParser(TestCase):
|
#class TestMultiPartParser(TestCase):
|
||||||
# def setUp(self):
|
# def setUp(self):
|
||||||
# self.req = RequestFactory()
|
# self.req = RequestFactory()
|
||||||
|
@ -145,18 +145,18 @@ class Form(forms.Form):
|
||||||
|
|
||||||
class TestFormParser(TestCase):
|
class TestFormParser(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.string = "field1=abc&field2=defghijk"
|
self.string = "field1=abc&field2=defghijk"
|
||||||
|
|
||||||
def test_parse(self):
|
def test_parse(self):
|
||||||
""" Make sure the `QueryDict` works OK """
|
""" Make sure the `QueryDict` works OK """
|
||||||
parser = FormParser(None)
|
parser = FormParser(None)
|
||||||
|
|
||||||
stream = StringIO(self.string)
|
stream = StringIO(self.string)
|
||||||
(data, files) = parser.parse(stream)
|
(data, files) = parser.parse(stream)
|
||||||
|
|
||||||
self.assertEqual(Form(data).is_valid(), True)
|
self.assertEqual(Form(data).is_valid(), True)
|
||||||
|
|
||||||
|
|
||||||
class TestXMLParser(TestCase):
|
class TestXMLParser(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.input = StringIO(
|
self.input = StringIO(
|
||||||
|
@ -164,18 +164,18 @@ class TestXMLParser(TestCase):
|
||||||
'<root>'
|
'<root>'
|
||||||
'<field_a>121.0</field_a>'
|
'<field_a>121.0</field_a>'
|
||||||
'<field_b>dasd</field_b>'
|
'<field_b>dasd</field_b>'
|
||||||
'<field_c></field_c>'
|
'<field_c></field_c>'
|
||||||
'<field_d>2011-12-25 12:45:00</field_d>'
|
'<field_d>2011-12-25 12:45:00</field_d>'
|
||||||
'</root>'
|
'</root>'
|
||||||
)
|
)
|
||||||
self.data = {
|
self.data = {
|
||||||
'field_a': 121,
|
'field_a': 121,
|
||||||
'field_b': 'dasd',
|
'field_b': 'dasd',
|
||||||
'field_c': None,
|
'field_c': None,
|
||||||
'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_parse(self):
|
def test_parse(self):
|
||||||
parser = XMLParser(None)
|
parser = XMLParser(None)
|
||||||
(data, files) = parser.parse(self.input)
|
(data, files) = parser.parse(self.input)
|
||||||
|
|
|
@ -41,7 +41,7 @@ class MockView(ResponseMixin, DjangoView):
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
response = Response(DUMMYSTATUS, DUMMYCONTENT)
|
response = Response(DUMMYSTATUS, DUMMYCONTENT)
|
||||||
return self.render(response)
|
return self.render(response)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
||||||
|
@ -92,7 +92,7 @@ class RendererIntegrationTests(TestCase):
|
||||||
self.assertEquals(resp['Content-Type'], RendererB.media_type)
|
self.assertEquals(resp['Content-Type'], RendererB.media_type)
|
||||||
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||||
|
|
||||||
def test_specified_renderer_serializes_content_on_accept_query(self):
|
def test_specified_renderer_serializes_content_on_accept_query(self):
|
||||||
"""The '_accept' query string should behave in the same way as the Accept header."""
|
"""The '_accept' query string should behave in the same way as the Accept header."""
|
||||||
resp = self.client.get('/?_accept=%s' % RendererB.media_type)
|
resp = self.client.get('/?_accept=%s' % RendererB.media_type)
|
||||||
|
@ -150,7 +150,7 @@ _flat_repr = '{"foo": ["bar", "baz"]}'
|
||||||
|
|
||||||
_indented_repr = """{
|
_indented_repr = """{
|
||||||
"foo": [
|
"foo": [
|
||||||
"bar",
|
"bar",
|
||||||
"baz"
|
"baz"
|
||||||
]
|
]
|
||||||
}"""
|
}"""
|
||||||
|
@ -172,13 +172,13 @@ class JSONRendererTests(TestCase):
|
||||||
|
|
||||||
def test_with_content_type_args(self):
|
def test_with_content_type_args(self):
|
||||||
"""
|
"""
|
||||||
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=2')
|
||||||
self.assertEquals(content, _indented_repr)
|
self.assertEquals(content, _indented_repr)
|
||||||
|
|
||||||
def test_render_and_parse(self):
|
def test_render_and_parse(self):
|
||||||
"""
|
"""
|
||||||
Test rendering and then parsing returns the original object.
|
Test rendering and then parsing returns the original object.
|
||||||
|
@ -191,19 +191,19 @@ class JSONRendererTests(TestCase):
|
||||||
|
|
||||||
content = renderer.render(obj, 'application/json')
|
content = renderer.render(obj, 'application/json')
|
||||||
(data, files) = parser.parse(StringIO(content))
|
(data, files) = parser.parse(StringIO(content))
|
||||||
self.assertEquals(obj, data)
|
self.assertEquals(obj, data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if YAMLRenderer:
|
if YAMLRenderer:
|
||||||
_yaml_repr = 'foo: [bar, baz]\n'
|
_yaml_repr = 'foo: [bar, baz]\n'
|
||||||
|
|
||||||
|
|
||||||
class YAMLRendererTests(TestCase):
|
class YAMLRendererTests(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests specific to the JSON Renderer
|
Tests specific to the JSON Renderer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_render(self):
|
def test_render(self):
|
||||||
"""
|
"""
|
||||||
Test basic YAML rendering.
|
Test basic YAML rendering.
|
||||||
|
@ -212,24 +212,24 @@ if YAMLRenderer:
|
||||||
renderer = YAMLRenderer(None)
|
renderer = YAMLRenderer(None)
|
||||||
content = renderer.render(obj, 'application/yaml')
|
content = renderer.render(obj, 'application/yaml')
|
||||||
self.assertEquals(content, _yaml_repr)
|
self.assertEquals(content, _yaml_repr)
|
||||||
|
|
||||||
|
|
||||||
def test_render_and_parse(self):
|
def test_render_and_parse(self):
|
||||||
"""
|
"""
|
||||||
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 = YAMLRenderer(None)
|
renderer = YAMLRenderer(None)
|
||||||
parser = YAMLParser(None)
|
parser = YAMLParser(None)
|
||||||
|
|
||||||
content = renderer.render(obj, 'application/yaml')
|
content = renderer.render(obj, 'application/yaml')
|
||||||
(data, files) = parser.parse(StringIO(content))
|
(data, files) = parser.parse(StringIO(content))
|
||||||
self.assertEquals(obj, data)
|
self.assertEquals(obj, data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class XMLRendererTestCase(TestCase):
|
class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests specific to the XML Renderer
|
Tests specific to the XML Renderer
|
||||||
|
@ -289,4 +289,4 @@ class XMLRendererTestCase(TestCase):
|
||||||
def assertXMLContains(self, xml, string):
|
def assertXMLContains(self, xml, string):
|
||||||
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
|
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
|
||||||
self.assertTrue(xml.endswith('</root>'))
|
self.assertTrue(xml.endswith('</root>'))
|
||||||
self.assertTrue(string in xml, '%r not in %r' % (string, xml))
|
self.assertTrue(string in xml, '%r not in %r' % (string, xml))
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#from djangorestframework.response import Response
|
#from djangorestframework.response import Response
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#class TestResponse(TestCase):
|
#class TestResponse(TestCase):
|
||||||
#
|
#
|
||||||
# # Interface tests
|
# # Interface tests
|
||||||
#
|
#
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.db import models
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
|
||||||
class TestObjectToData(TestCase):
|
class TestObjectToData(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests for the Serializer class.
|
Tests for the Serializer class.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.test import TestCase
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
|
|
||||||
|
|
||||||
class TestStatus(TestCase):
|
class TestStatus(TestCase):
|
||||||
"""Simple sanity test to check the status module"""
|
"""Simple sanity test to check the status module"""
|
||||||
|
|
||||||
def test_status(self):
|
def test_status(self):
|
||||||
|
|
|
@ -21,25 +21,25 @@ class MockView(View):
|
||||||
class MockView_PerViewThrottling(MockView):
|
class MockView_PerViewThrottling(MockView):
|
||||||
permissions = ( PerViewThrottling, )
|
permissions = ( PerViewThrottling, )
|
||||||
|
|
||||||
class MockView_PerResourceThrottling(MockView):
|
class MockView_PerResourceThrottling(MockView):
|
||||||
permissions = ( PerResourceThrottling, )
|
permissions = ( PerResourceThrottling, )
|
||||||
resource = FormResource
|
resource = FormResource
|
||||||
|
|
||||||
class MockView_MinuteThrottling(MockView):
|
class MockView_MinuteThrottling(MockView):
|
||||||
throttle = '3/min'
|
throttle = '3/min'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ThrottlingTests(TestCase):
|
class ThrottlingTests(TestCase):
|
||||||
urls = 'djangorestframework.tests.throttling'
|
urls = 'djangorestframework.tests.throttling'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""
|
"""
|
||||||
Reset the cache so that no throttles will be active
|
Reset the cache so that no throttles will be active
|
||||||
"""
|
"""
|
||||||
cache.clear()
|
cache.clear()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
def test_requests_are_throttled(self):
|
def test_requests_are_throttled(self):
|
||||||
"""
|
"""
|
||||||
Ensure request rate is limited
|
Ensure request rate is limited
|
||||||
|
@ -48,7 +48,7 @@ class ThrottlingTests(TestCase):
|
||||||
for dummy in range(4):
|
for dummy in range(4):
|
||||||
response = MockView.as_view()(request)
|
response = MockView.as_view()(request)
|
||||||
self.assertEqual(503, response.status_code)
|
self.assertEqual(503, response.status_code)
|
||||||
|
|
||||||
def set_throttle_timer(self, view, value):
|
def set_throttle_timer(self, view, value):
|
||||||
"""
|
"""
|
||||||
Explicitly set the timer, overriding time.time()
|
Explicitly set the timer, overriding time.time()
|
||||||
|
@ -71,7 +71,7 @@ class ThrottlingTests(TestCase):
|
||||||
|
|
||||||
response = MockView.as_view()(request)
|
response = MockView.as_view()(request)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
def ensure_is_throttled(self, view, expect):
|
def ensure_is_throttled(self, view, expect):
|
||||||
request = self.factory.get('/')
|
request = self.factory.get('/')
|
||||||
request.user = User.objects.create(username='a')
|
request.user = User.objects.create(username='a')
|
||||||
|
@ -80,27 +80,27 @@ class ThrottlingTests(TestCase):
|
||||||
request.user = User.objects.create(username='b')
|
request.user = User.objects.create(username='b')
|
||||||
response = view.as_view()(request)
|
response = view.as_view()(request)
|
||||||
self.assertEqual(expect, response.status_code)
|
self.assertEqual(expect, response.status_code)
|
||||||
|
|
||||||
def test_request_throttling_is_per_user(self):
|
def test_request_throttling_is_per_user(self):
|
||||||
"""
|
"""
|
||||||
Ensure request rate is only limited per user, not globally for
|
Ensure request rate is only limited per user, not globally for
|
||||||
PerUserThrottles
|
PerUserThrottles
|
||||||
"""
|
"""
|
||||||
self.ensure_is_throttled(MockView, 200)
|
self.ensure_is_throttled(MockView, 200)
|
||||||
|
|
||||||
def test_request_throttling_is_per_view(self):
|
def test_request_throttling_is_per_view(self):
|
||||||
"""
|
"""
|
||||||
Ensure request rate is limited globally per View for PerViewThrottles
|
Ensure request rate is limited globally per View for PerViewThrottles
|
||||||
"""
|
"""
|
||||||
self.ensure_is_throttled(MockView_PerViewThrottling, 503)
|
self.ensure_is_throttled(MockView_PerViewThrottling, 503)
|
||||||
|
|
||||||
def test_request_throttling_is_per_resource(self):
|
def test_request_throttling_is_per_resource(self):
|
||||||
"""
|
"""
|
||||||
Ensure request rate is limited globally per Resource for PerResourceThrottles
|
Ensure request rate is limited globally per Resource for PerResourceThrottles
|
||||||
"""
|
"""
|
||||||
self.ensure_is_throttled(MockView_PerResourceThrottling, 503)
|
self.ensure_is_throttled(MockView_PerResourceThrottling, 503)
|
||||||
|
|
||||||
|
|
||||||
def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers):
|
def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers):
|
||||||
"""
|
"""
|
||||||
Ensure the response returns an X-Throttle field with status and next attributes
|
Ensure the response returns an X-Throttle field with status and next attributes
|
||||||
|
@ -111,7 +111,7 @@ class ThrottlingTests(TestCase):
|
||||||
self.set_throttle_timer(view, timer)
|
self.set_throttle_timer(view, timer)
|
||||||
response = view.as_view()(request)
|
response = view.as_view()(request)
|
||||||
self.assertEquals(response['X-Throttle'], expect)
|
self.assertEquals(response['X-Throttle'], expect)
|
||||||
|
|
||||||
def test_seconds_fields(self):
|
def test_seconds_fields(self):
|
||||||
"""
|
"""
|
||||||
Ensure for second based throttles.
|
Ensure for second based throttles.
|
||||||
|
@ -122,7 +122,7 @@ class ThrottlingTests(TestCase):
|
||||||
(0, 'status=SUCCESS; next=1.00 sec'),
|
(0, 'status=SUCCESS; next=1.00 sec'),
|
||||||
(0, 'status=FAILURE; next=1.00 sec')
|
(0, 'status=FAILURE; next=1.00 sec')
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_minutes_fields(self):
|
def test_minutes_fields(self):
|
||||||
"""
|
"""
|
||||||
Ensure for minute based throttles.
|
Ensure for minute based throttles.
|
||||||
|
@ -133,7 +133,7 @@ class ThrottlingTests(TestCase):
|
||||||
(0, 'status=SUCCESS; next=60.00 sec'),
|
(0, 'status=SUCCESS; next=60.00 sec'),
|
||||||
(0, 'status=FAILURE; next=60.00 sec')
|
(0, 'status=FAILURE; next=60.00 sec')
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_next_rate_remains_constant_if_followed(self):
|
def test_next_rate_remains_constant_if_followed(self):
|
||||||
"""
|
"""
|
||||||
If a client follows the recommended next request rate,
|
If a client follows the recommended next request rate,
|
||||||
|
|
|
@ -22,7 +22,7 @@ class TestDisabledValidations(TestCase):
|
||||||
resource = DisabledFormResource
|
resource = DisabledFormResource
|
||||||
|
|
||||||
view = MockView()
|
view = MockView()
|
||||||
content = {'qwerty':'uiop'}
|
content = {'qwerty':'uiop'}
|
||||||
self.assertEqual(FormResource(view).validate_request(content, None), content)
|
self.assertEqual(FormResource(view).validate_request(content, None), content)
|
||||||
|
|
||||||
def test_disabled_form_validator_get_bound_form_returns_none(self):
|
def test_disabled_form_validator_get_bound_form_returns_none(self):
|
||||||
|
@ -33,12 +33,12 @@ class TestDisabledValidations(TestCase):
|
||||||
|
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
resource = DisabledFormResource
|
resource = DisabledFormResource
|
||||||
|
|
||||||
view = MockView()
|
view = MockView()
|
||||||
content = {'qwerty':'uiop'}
|
content = {'qwerty':'uiop'}
|
||||||
self.assertEqual(FormResource(view).get_bound_form(content), None)
|
self.assertEqual(FormResource(view).get_bound_form(content), None)
|
||||||
|
|
||||||
|
|
||||||
def test_disabled_model_form_validator_returns_content_unchanged(self):
|
def test_disabled_model_form_validator_returns_content_unchanged(self):
|
||||||
"""If the view's form is None and does not have a Resource with a model set then
|
"""If the view's form is None and does not have a Resource with a model set then
|
||||||
ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
|
ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
|
||||||
|
@ -47,17 +47,17 @@ class TestDisabledValidations(TestCase):
|
||||||
resource = ModelResource
|
resource = ModelResource
|
||||||
|
|
||||||
view = DisabledModelFormView()
|
view = DisabledModelFormView()
|
||||||
content = {'qwerty':'uiop'}
|
content = {'qwerty':'uiop'}
|
||||||
self.assertEqual(ModelResource(view).get_bound_form(content), None)#
|
self.assertEqual(ModelResource(view).get_bound_form(content), None)#
|
||||||
|
|
||||||
def test_disabled_model_form_validator_get_bound_form_returns_none(self):
|
def test_disabled_model_form_validator_get_bound_form_returns_none(self):
|
||||||
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
|
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
|
||||||
class DisabledModelFormView(View):
|
class DisabledModelFormView(View):
|
||||||
resource = ModelResource
|
resource = ModelResource
|
||||||
|
|
||||||
view = DisabledModelFormView()
|
view = DisabledModelFormView()
|
||||||
content = {'qwerty':'uiop'}
|
content = {'qwerty':'uiop'}
|
||||||
self.assertEqual(ModelResource(view).get_bound_form(content), None)
|
self.assertEqual(ModelResource(view).get_bound_form(content), None)
|
||||||
|
|
||||||
class TestNonFieldErrors(TestCase):
|
class TestNonFieldErrors(TestCase):
|
||||||
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
|
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
|
||||||
|
@ -68,15 +68,15 @@ class TestNonFieldErrors(TestCase):
|
||||||
field1 = forms.CharField(required=False)
|
field1 = forms.CharField(required=False)
|
||||||
field2 = forms.CharField(required=False)
|
field2 = forms.CharField(required=False)
|
||||||
ERROR_TEXT = 'You may not supply both field1 and field2'
|
ERROR_TEXT = 'You may not supply both field1 and field2'
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data:
|
if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data:
|
||||||
raise forms.ValidationError(self.ERROR_TEXT)
|
raise forms.ValidationError(self.ERROR_TEXT)
|
||||||
return self.cleaned_data #pragma: no cover
|
return self.cleaned_data #pragma: no cover
|
||||||
|
|
||||||
class MockResource(FormResource):
|
class MockResource(FormResource):
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class TestNonFieldErrors(TestCase):
|
||||||
content = {'field1': 'example1', 'field2': 'example2'}
|
content = {'field1': 'example1', 'field2': 'example2'}
|
||||||
try:
|
try:
|
||||||
MockResource(view).validate_request(content, None)
|
MockResource(view).validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
except ErrorResponse, exc:
|
||||||
self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
||||||
else:
|
else:
|
||||||
self.fail('ErrorResponse was not raised') #pragma: no cover
|
self.fail('ErrorResponse was not raised') #pragma: no cover
|
||||||
|
@ -94,26 +94,26 @@ class TestFormValidation(TestCase):
|
||||||
"""Tests which check basic form validation.
|
"""Tests which check basic form validation.
|
||||||
Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
|
Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
|
||||||
(ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)"""
|
(ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
class MockForm(forms.Form):
|
class MockForm(forms.Form):
|
||||||
qwerty = forms.CharField(required=True)
|
qwerty = forms.CharField(required=True)
|
||||||
|
|
||||||
class MockFormResource(FormResource):
|
class MockFormResource(FormResource):
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockModelResource(ModelResource):
|
class MockModelResource(ModelResource):
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockFormView(View):
|
class MockFormView(View):
|
||||||
resource = MockFormResource
|
resource = MockFormResource
|
||||||
|
|
||||||
class MockModelFormView(View):
|
class MockModelFormView(View):
|
||||||
resource = MockModelResource
|
resource = MockModelResource
|
||||||
|
|
||||||
self.MockFormResource = MockFormResource
|
self.MockFormResource = MockFormResource
|
||||||
self.MockModelResource = MockModelResource
|
self.MockModelResource = MockModelResource
|
||||||
self.MockFormView = MockFormView
|
self.MockFormView = MockFormView
|
||||||
self.MockModelFormView = MockModelFormView
|
self.MockModelFormView = MockModelFormView
|
||||||
|
|
||||||
|
|
||||||
def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
|
def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
|
||||||
|
@ -130,22 +130,22 @@ class TestFormValidation(TestCase):
|
||||||
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
||||||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||||
self.assertRaises(ErrorResponse, validator.validate_request, content, None)
|
self.assertRaises(ErrorResponse, validator.validate_request, content, None)
|
||||||
|
|
||||||
def validation_allows_extra_fields_if_explicitly_set(self, validator):
|
def validation_allows_extra_fields_if_explicitly_set(self, validator):
|
||||||
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
|
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
|
||||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||||
validator._validate(content, None, allowed_extra_fields=('extra',))
|
validator._validate(content, None, allowed_extra_fields=('extra',))
|
||||||
|
|
||||||
def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
|
def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
|
||||||
"""If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
|
"""If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
|
||||||
content = {'qwerty': 'uiop'}
|
content = {'qwerty': 'uiop'}
|
||||||
self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
|
self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
|
||||||
|
|
||||||
def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
|
def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
|
||||||
"""If validation fails due to no content, ensure the response contains a single non-field error"""
|
"""If validation fails due to no content, ensure the response contains a single non-field error"""
|
||||||
content = {}
|
content = {}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
except ErrorResponse, exc:
|
||||||
|
@ -158,7 +158,7 @@ class TestFormValidation(TestCase):
|
||||||
content = {'qwerty': ''}
|
content = {'qwerty': ''}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
except ErrorResponse, exc:
|
||||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}})
|
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised') #pragma: no cover
|
self.fail('ResourceException was not raised') #pragma: no cover
|
||||||
|
@ -168,17 +168,17 @@ class TestFormValidation(TestCase):
|
||||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
except ErrorResponse, exc:
|
||||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}})
|
self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised') #pragma: no cover
|
self.fail('ResourceException was not raised') #pragma: no cover
|
||||||
|
|
||||||
def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator):
|
def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator):
|
||||||
"""If validation for multiple reasons, ensure the response contains each error"""
|
"""If validation for multiple reasons, ensure the response contains each error"""
|
||||||
content = {'qwerty': '', 'extra': 'extra'}
|
content = {'qwerty': '', 'extra': 'extra'}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, exc:
|
except ErrorResponse, exc:
|
||||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'],
|
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'],
|
||||||
'extra': ['This field does not exist.']}})
|
'extra': ['This field does not exist.']}})
|
||||||
else:
|
else:
|
||||||
|
@ -263,23 +263,23 @@ class TestFormValidation(TestCase):
|
||||||
|
|
||||||
class TestModelFormValidator(TestCase):
|
class TestModelFormValidator(TestCase):
|
||||||
"""Tests specific to ModelFormValidatorMixin"""
|
"""Tests specific to ModelFormValidatorMixin"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create a validator for a model with two fields and a property."""
|
"""Create a validator for a model with two fields and a property."""
|
||||||
class MockModel(models.Model):
|
class MockModel(models.Model):
|
||||||
qwerty = models.CharField(max_length=256)
|
qwerty = models.CharField(max_length=256)
|
||||||
uiop = models.CharField(max_length=256, blank=True)
|
uiop = models.CharField(max_length=256, blank=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def readonly(self):
|
def readonly(self):
|
||||||
return 'read only'
|
return 'read only'
|
||||||
|
|
||||||
class MockResource(ModelResource):
|
class MockResource(ModelResource):
|
||||||
model = MockModel
|
model = MockModel
|
||||||
|
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
resource = MockResource
|
resource = MockResource
|
||||||
|
|
||||||
self.validator = MockResource(MockView)
|
self.validator = MockResource(MockView)
|
||||||
|
|
||||||
|
|
||||||
|
@ -299,19 +299,19 @@ class TestModelFormValidator(TestCase):
|
||||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||||
content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}
|
content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}
|
||||||
self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
|
self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
|
||||||
|
|
||||||
def test_validate_requires_fields_on_model_forms(self):
|
def test_validate_requires_fields_on_model_forms(self):
|
||||||
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
||||||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||||
content = {'readonly': 'read only'}
|
content = {'readonly': 'read only'}
|
||||||
self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
|
self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
|
||||||
|
|
||||||
def test_validate_does_not_require_blankable_fields_on_model_forms(self):
|
def test_validate_does_not_require_blankable_fields_on_model_forms(self):
|
||||||
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
|
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
|
||||||
content = {'qwerty':'example', 'readonly': 'read only'}
|
content = {'qwerty':'example', 'readonly': 'read only'}
|
||||||
self.validator.validate_request(content, None)
|
self.validator.validate_request(content, None)
|
||||||
|
|
||||||
def test_model_form_validator_uses_model_forms(self):
|
def test_model_form_validator_uses_model_forms(self):
|
||||||
self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))
|
self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))
|
||||||
|
|
||||||
|
|
|
@ -23,17 +23,17 @@ class ResourceMockView(View):
|
||||||
foo = forms.BooleanField(required=False)
|
foo = forms.BooleanField(required=False)
|
||||||
bar = forms.IntegerField(help_text='Must be an integer.')
|
bar = forms.IntegerField(help_text='Must be an integer.')
|
||||||
baz = forms.CharField(max_length=32)
|
baz = forms.CharField(max_length=32)
|
||||||
|
|
||||||
form = MockForm
|
form = MockForm
|
||||||
|
|
||||||
class MockResource(ModelResource):
|
class MockResource(ModelResource):
|
||||||
"""This is a mock model-based resource"""
|
"""This is a mock model-based resource"""
|
||||||
|
|
||||||
class MockResourceModel(models.Model):
|
class MockResourceModel(models.Model):
|
||||||
foo = models.BooleanField()
|
foo = models.BooleanField()
|
||||||
bar = models.IntegerField(help_text='Must be an integer.')
|
bar = models.IntegerField(help_text='Must be an integer.')
|
||||||
baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
|
baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
|
||||||
|
|
||||||
model = MockResourceModel
|
model = MockResourceModel
|
||||||
fields = ('foo', 'bar', 'baz')
|
fields = ('foo', 'bar', 'baz')
|
||||||
|
|
||||||
|
@ -50,62 +50,62 @@ urlpatterns = patterns('djangorestframework.utils.staticviews',
|
||||||
|
|
||||||
class BaseViewTests(TestCase):
|
class BaseViewTests(TestCase):
|
||||||
"""Test the base view class of djangorestframework"""
|
"""Test the base view class of djangorestframework"""
|
||||||
urls = 'djangorestframework.tests.views'
|
urls = 'djangorestframework.tests.views'
|
||||||
|
|
||||||
def test_options_method_simple_view(self):
|
def test_options_method_simple_view(self):
|
||||||
response = self.client.options('/mock/')
|
response = self.client.options('/mock/')
|
||||||
self._verify_options_response(response,
|
self._verify_options_response(response,
|
||||||
name='Mock',
|
name='Mock',
|
||||||
description='This is a basic mock view')
|
description='This is a basic mock view')
|
||||||
|
|
||||||
def test_options_method_resource_view(self):
|
def test_options_method_resource_view(self):
|
||||||
response = self.client.options('/resourcemock/')
|
response = self.client.options('/resourcemock/')
|
||||||
self._verify_options_response(response,
|
self._verify_options_response(response,
|
||||||
name='Resource Mock',
|
name='Resource Mock',
|
||||||
description='This is a resource-based mock view',
|
description='This is a resource-based mock view',
|
||||||
fields={'foo':'BooleanField',
|
fields={'foo':'BooleanField',
|
||||||
'bar':'IntegerField',
|
'bar':'IntegerField',
|
||||||
'baz':'CharField',
|
'baz':'CharField',
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_options_method_model_resource_list_view(self):
|
def test_options_method_model_resource_list_view(self):
|
||||||
response = self.client.options('/model/')
|
response = self.client.options('/model/')
|
||||||
self._verify_options_response(response,
|
self._verify_options_response(response,
|
||||||
name='Mock List',
|
name='Mock List',
|
||||||
description='This is a mock model-based resource',
|
description='This is a mock model-based resource',
|
||||||
fields={'foo':'BooleanField',
|
fields={'foo':'BooleanField',
|
||||||
'bar':'IntegerField',
|
'bar':'IntegerField',
|
||||||
'baz':'CharField',
|
'baz':'CharField',
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_options_method_model_resource_detail_view(self):
|
def test_options_method_model_resource_detail_view(self):
|
||||||
response = self.client.options('/model/0/')
|
response = self.client.options('/model/0/')
|
||||||
self._verify_options_response(response,
|
self._verify_options_response(response,
|
||||||
name='Mock Instance',
|
name='Mock Instance',
|
||||||
description='This is a mock model-based resource',
|
description='This is a mock model-based resource',
|
||||||
fields={'foo':'BooleanField',
|
fields={'foo':'BooleanField',
|
||||||
'bar':'IntegerField',
|
'bar':'IntegerField',
|
||||||
'baz':'CharField',
|
'baz':'CharField',
|
||||||
})
|
})
|
||||||
|
|
||||||
def _verify_options_response(self, response, name, description, fields=None, status=200,
|
def _verify_options_response(self, response, name, description, fields=None, status=200,
|
||||||
mime_type='application/json'):
|
mime_type='application/json'):
|
||||||
self.assertEqual(response.status_code, status)
|
self.assertEqual(response.status_code, status)
|
||||||
self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
|
self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
|
||||||
parser = JSONParser(None)
|
parser = JSONParser(None)
|
||||||
(data, files) = parser.parse(StringIO(response.content))
|
(data, files) = parser.parse(StringIO(response.content))
|
||||||
self.assertTrue('application/json' in data['renders'])
|
self.assertTrue('application/json' in data['renders'])
|
||||||
self.assertEqual(name, data['name'])
|
self.assertEqual(name, data['name'])
|
||||||
self.assertEqual(description, data['description'])
|
self.assertEqual(description, data['description'])
|
||||||
if fields is None:
|
if fields is None:
|
||||||
self.assertFalse(hasattr(data, 'fields'))
|
self.assertFalse(hasattr(data, 'fields'))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(data['fields'], fields)
|
self.assertEqual(data['fields'], fields)
|
||||||
|
|
||||||
|
|
||||||
class ExtraViewsTests(TestCase):
|
class ExtraViewsTests(TestCase):
|
||||||
"""Test the extra views djangorestframework provides"""
|
"""Test the extra views djangorestframework provides"""
|
||||||
urls = 'djangorestframework.tests.views'
|
urls = 'djangorestframework.tests.views'
|
||||||
|
|
||||||
def test_robots_view(self):
|
def test_robots_view(self):
|
||||||
"""Ensure the robots view exists"""
|
"""Ensure the robots view exists"""
|
||||||
|
|
|
@ -36,7 +36,7 @@ def as_tuple(obj):
|
||||||
return obj
|
return obj
|
||||||
return (obj,)
|
return (obj,)
|
||||||
|
|
||||||
|
|
||||||
def url_resolves(url):
|
def url_resolves(url):
|
||||||
"""
|
"""
|
||||||
Return True if the given URL is mapped to a view in the urlconf, False otherwise.
|
Return True if the given URL is mapped to a view in the urlconf, False otherwise.
|
||||||
|
@ -50,7 +50,7 @@ def url_resolves(url):
|
||||||
|
|
||||||
# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
|
# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
|
||||||
#class object_dict(dict):
|
#class object_dict(dict):
|
||||||
# """object view of dict, you can
|
# """object view of dict, you can
|
||||||
# >>> a = object_dict()
|
# >>> a = object_dict()
|
||||||
# >>> a.fish = 'fish'
|
# >>> a.fish = 'fish'
|
||||||
# >>> a['fish']
|
# >>> a['fish']
|
||||||
|
@ -103,8 +103,8 @@ class XML2Dict(object):
|
||||||
old = node_tree[tag]
|
old = node_tree[tag]
|
||||||
if not isinstance(old, list):
|
if not isinstance(old, list):
|
||||||
node_tree.pop(tag)
|
node_tree.pop(tag)
|
||||||
node_tree[tag] = [old] # multi times, so change old dict to a list
|
node_tree[tag] = [old] # multi times, so change old dict to a list
|
||||||
node_tree[tag].append(tree) # add the new one
|
node_tree[tag].append(tree) # add the new one
|
||||||
|
|
||||||
return node_tree
|
return node_tree
|
||||||
|
|
||||||
|
@ -117,13 +117,13 @@ class XML2Dict(object):
|
||||||
"""
|
"""
|
||||||
result = re.compile("\{(.*)\}(.*)").search(tag)
|
result = re.compile("\{(.*)\}(.*)").search(tag)
|
||||||
if result:
|
if result:
|
||||||
value.namespace, tag = result.groups()
|
value.namespace, tag = result.groups()
|
||||||
return (tag, value)
|
return (tag, value)
|
||||||
|
|
||||||
def parse(self, file):
|
def parse(self, file):
|
||||||
"""parse a xml file to a dict"""
|
"""parse a xml file to a dict"""
|
||||||
f = open(file, 'r')
|
f = open(file, 'r')
|
||||||
return self.fromstring(f.read())
|
return self.fromstring(f.read())
|
||||||
|
|
||||||
def fromstring(self, s):
|
def fromstring(self, s):
|
||||||
"""parse a string"""
|
"""parse a string"""
|
||||||
|
@ -150,16 +150,16 @@ class XMLRenderer():
|
||||||
xml.startElement(key, {})
|
xml.startElement(key, {})
|
||||||
self._to_xml(xml, value)
|
self._to_xml(xml, value)
|
||||||
xml.endElement(key)
|
xml.endElement(key)
|
||||||
|
|
||||||
elif data is None:
|
elif data is None:
|
||||||
# Don't output any value
|
# Don't output any value
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
xml.characters(smart_unicode(data))
|
xml.characters(smart_unicode(data))
|
||||||
|
|
||||||
def dict2xml(self, data):
|
def dict2xml(self, data):
|
||||||
stream = StringIO.StringIO()
|
stream = StringIO.StringIO()
|
||||||
|
|
||||||
xml = SimplerXMLGenerator(stream, "utf-8")
|
xml = SimplerXMLGenerator(stream, "utf-8")
|
||||||
xml.startDocument()
|
xml.startDocument()
|
||||||
|
|
|
@ -3,12 +3,12 @@ from djangorestframework.utils.description import get_name
|
||||||
|
|
||||||
def get_breadcrumbs(url):
|
def get_breadcrumbs(url):
|
||||||
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
|
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
|
||||||
|
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
|
|
||||||
def breadcrumbs_recursive(url, breadcrumbs_list):
|
def breadcrumbs_recursive(url, breadcrumbs_list):
|
||||||
"""Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url."""
|
"""Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(view, unused_args, unused_kwargs) = resolve(url)
|
(view, unused_args, unused_kwargs) = resolve(url)
|
||||||
except:
|
except:
|
||||||
|
@ -17,15 +17,15 @@ def get_breadcrumbs(url):
|
||||||
# Check if this is a REST framework view, and if so add it to the breadcrumbs
|
# Check if this is a REST framework view, and if so add it to the breadcrumbs
|
||||||
if isinstance(getattr(view, 'cls_instance', None), View):
|
if isinstance(getattr(view, 'cls_instance', None), View):
|
||||||
breadcrumbs_list.insert(0, (get_name(view), url))
|
breadcrumbs_list.insert(0, (get_name(view), url))
|
||||||
|
|
||||||
if url == '':
|
if url == '':
|
||||||
# All done
|
# All done
|
||||||
return breadcrumbs_list
|
return breadcrumbs_list
|
||||||
|
|
||||||
elif url.endswith('/'):
|
elif url.endswith('/'):
|
||||||
# Drop trailing slash off the end and continue to try to resolve more breadcrumbs
|
# Drop trailing slash off the end and continue to try to resolve more breadcrumbs
|
||||||
return breadcrumbs_recursive(url.rstrip('/'), breadcrumbs_list)
|
return breadcrumbs_recursive(url.rstrip('/'), breadcrumbs_list)
|
||||||
|
|
||||||
# Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs
|
# Drop trailing non-slash off the end and continue to try to resolve more breadcrumbs
|
||||||
return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list)
|
return breadcrumbs_recursive(url[:url.rfind('/') + 1], breadcrumbs_list)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from djangorestframework.resources import Resource, FormResource, ModelResource
|
||||||
def get_name(view):
|
def get_name(view):
|
||||||
"""
|
"""
|
||||||
Return a name for the view.
|
Return a name for the view.
|
||||||
|
|
||||||
If view has a name attribute, use that, otherwise use the view's class name, with 'CamelCaseNames' converted to 'Camel Case Names'.
|
If view has a name attribute, use that, otherwise use the view's class name, with 'CamelCaseNames' converted to 'Camel Case Names'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ def get_name(view):
|
||||||
# If this view has a resource that's been overridden, then use that resource for the name
|
# If this view has a resource that's been overridden, then use that resource for the name
|
||||||
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
|
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
|
||||||
name = view.resource.__name__
|
name = view.resource.__name__
|
||||||
|
|
||||||
# Chomp of any non-descriptive trailing part of the resource class name
|
# Chomp of any non-descriptive trailing part of the resource class name
|
||||||
if name.endswith('Resource') and name != 'Resource':
|
if name.endswith('Resource') and name != 'Resource':
|
||||||
name = name[:-len('Resource')]
|
name = name[:-len('Resource')]
|
||||||
|
@ -30,7 +30,7 @@ def get_name(view):
|
||||||
# If the view has a descriptive suffix, eg '*** List', '*** Instance'
|
# If the view has a descriptive suffix, eg '*** List', '*** Instance'
|
||||||
if getattr(view, '_suffix', None):
|
if getattr(view, '_suffix', None):
|
||||||
name += view._suffix
|
name += view._suffix
|
||||||
|
|
||||||
# Otherwise if it's a function view use the function's name
|
# Otherwise if it's a function view use the function's name
|
||||||
elif getattr(view, '__name__', None) is not None:
|
elif getattr(view, '__name__', None) is not None:
|
||||||
name = view.__name__
|
name = view.__name__
|
||||||
|
@ -62,12 +62,12 @@ def get_description(view):
|
||||||
# grok the class instance that we stored when as_view was called.
|
# grok the class instance that we stored when as_view was called.
|
||||||
if getattr(view, 'cls_instance', None):
|
if getattr(view, 'cls_instance', None):
|
||||||
view = view.cls_instance
|
view = view.cls_instance
|
||||||
|
|
||||||
|
|
||||||
# If this view has a resource that's been overridden, then use the resource's doctring
|
# If this view has a resource that's been overridden, then use the resource's doctring
|
||||||
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
|
if getattr(view, 'resource', None) not in (None, Resource, FormResource, ModelResource):
|
||||||
doc = view.resource.__doc__
|
doc = view.resource.__doc__
|
||||||
|
|
||||||
# Otherwise use the view doctring
|
# Otherwise use the view doctring
|
||||||
elif getattr(view, '__doc__', None):
|
elif getattr(view, '__doc__', None):
|
||||||
doc = view.__doc__
|
doc = view.__doc__
|
||||||
|
@ -81,11 +81,11 @@ def get_description(view):
|
||||||
|
|
||||||
whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()]
|
whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in doc.splitlines()[1:] if line.lstrip()]
|
||||||
|
|
||||||
# unindent the docstring if needed
|
# unindent the docstring if needed
|
||||||
if whitespace_counts:
|
if whitespace_counts:
|
||||||
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
|
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
|
||||||
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc)
|
return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', doc)
|
||||||
|
|
||||||
# otherwise return it as-is
|
# otherwise return it as-is
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ class _MediaType(object):
|
||||||
# return Decimal(self.params.get('q', '1.0'))
|
# return Decimal(self.params.get('q', '1.0'))
|
||||||
# except:
|
# except:
|
||||||
# return Decimal(0)
|
# return Decimal(0)
|
||||||
|
|
||||||
#def score(self):
|
#def score(self):
|
||||||
# """
|
# """
|
||||||
# Return an overall score for a given media type given it's quality and precedence.
|
# Return an overall score for a given media type given it's quality and precedence.
|
||||||
|
@ -119,7 +119,7 @@ class _MediaType(object):
|
||||||
# # NB. quality values should only have up to 3 decimal points
|
# # NB. quality values should only have up to 3 decimal points
|
||||||
# # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
|
# # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
|
||||||
# return self.quality * 10000 + self.precedence
|
# return self.quality * 10000 + self.precedence
|
||||||
|
|
||||||
#def as_tuple(self):
|
#def as_tuple(self):
|
||||||
# return (self.main_type, self.sub_type, self.params)
|
# return (self.main_type, self.sub_type, self.params)
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,16 @@ class BlogPostResource(ModelResource):
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
|
||||||
def comments(self, instance):
|
def comments(self, instance):
|
||||||
return reverse('comments', kwargs={'blogpost': instance.key})
|
return reverse('comments', kwargs={'blogpost': instance.key})
|
||||||
|
|
||||||
|
|
||||||
class CommentResource(ModelResource):
|
class CommentResource(ModelResource):
|
||||||
"""
|
"""
|
||||||
A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
|
A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
|
||||||
"""
|
"""
|
||||||
model = Comment
|
model = Comment
|
||||||
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
|
||||||
def blogpost(self, instance):
|
def blogpost(self, instance):
|
||||||
return reverse('blog-post', kwargs={'key': instance.blogpost.key})
|
return reverse('blog-post', kwargs={'key': instance.blogpost.key})
|
|
@ -15,68 +15,68 @@ from blogpost import models, urls
|
||||||
|
|
||||||
# class AcceptHeaderTests(TestCase):
|
# class AcceptHeaderTests(TestCase):
|
||||||
# """Test correct behaviour of the Accept header as specified by RFC 2616:
|
# """Test correct behaviour of the Accept header as specified by RFC 2616:
|
||||||
#
|
#
|
||||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"""
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"""
|
||||||
#
|
#
|
||||||
# def assert_accept_mimetype(self, mimetype, expect=None):
|
# def assert_accept_mimetype(self, mimetype, expect=None):
|
||||||
# """Assert that a request with given mimetype in the accept header,
|
# """Assert that a request with given mimetype in the accept header,
|
||||||
# gives a response with the appropriate content-type."""
|
# gives a response with the appropriate content-type."""
|
||||||
# if expect is None:
|
# if expect is None:
|
||||||
# expect = mimetype
|
# expect = mimetype
|
||||||
#
|
#
|
||||||
# resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype)
|
# resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype)
|
||||||
#
|
#
|
||||||
# self.assertEquals(resp['content-type'], expect)
|
# self.assertEquals(resp['content-type'], expect)
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# def dont_test_accept_json(self):
|
# def dont_test_accept_json(self):
|
||||||
# """Ensure server responds with Content-Type of JSON when requested."""
|
# """Ensure server responds with Content-Type of JSON when requested."""
|
||||||
# self.assert_accept_mimetype('application/json')
|
# self.assert_accept_mimetype('application/json')
|
||||||
#
|
#
|
||||||
# def dont_test_accept_xml(self):
|
# def dont_test_accept_xml(self):
|
||||||
# """Ensure server responds with Content-Type of XML when requested."""
|
# """Ensure server responds with Content-Type of XML when requested."""
|
||||||
# self.assert_accept_mimetype('application/xml')
|
# self.assert_accept_mimetype('application/xml')
|
||||||
#
|
#
|
||||||
# def dont_test_accept_json_when_prefered_to_xml(self):
|
# def dont_test_accept_json_when_prefered_to_xml(self):
|
||||||
# """Ensure server responds with Content-Type of JSON when it is the client's prefered choice."""
|
# """Ensure server responds with Content-Type of JSON when it is the client's prefered choice."""
|
||||||
# self.assert_accept_mimetype('application/json;q=0.9, application/xml;q=0.1', expect='application/json')
|
# self.assert_accept_mimetype('application/json;q=0.9, application/xml;q=0.1', expect='application/json')
|
||||||
#
|
#
|
||||||
# def dont_test_accept_xml_when_prefered_to_json(self):
|
# def dont_test_accept_xml_when_prefered_to_json(self):
|
||||||
# """Ensure server responds with Content-Type of XML when it is the client's prefered choice."""
|
# """Ensure server responds with Content-Type of XML when it is the client's prefered choice."""
|
||||||
# self.assert_accept_mimetype('application/json;q=0.1, application/xml;q=0.9', expect='application/xml')
|
# self.assert_accept_mimetype('application/json;q=0.1, application/xml;q=0.9', expect='application/xml')
|
||||||
#
|
#
|
||||||
# def dont_test_default_json_prefered(self):
|
# def dont_test_default_json_prefered(self):
|
||||||
# """Ensure server responds with JSON in preference to XML."""
|
# """Ensure server responds with JSON in preference to XML."""
|
||||||
# self.assert_accept_mimetype('application/json,application/xml', expect='application/json')
|
# self.assert_accept_mimetype('application/json,application/xml', expect='application/json')
|
||||||
#
|
#
|
||||||
# def dont_test_accept_generic_subtype_format(self):
|
# def dont_test_accept_generic_subtype_format(self):
|
||||||
# """Ensure server responds with an appropriate type, when the subtype is left generic."""
|
# """Ensure server responds with an appropriate type, when the subtype is left generic."""
|
||||||
# self.assert_accept_mimetype('text/*', expect='text/html')
|
# self.assert_accept_mimetype('text/*', expect='text/html')
|
||||||
#
|
#
|
||||||
# def dont_test_accept_generic_type_format(self):
|
# def dont_test_accept_generic_type_format(self):
|
||||||
# """Ensure server responds with an appropriate type, when the type and subtype are left generic."""
|
# """Ensure server responds with an appropriate type, when the type and subtype are left generic."""
|
||||||
# self.assert_accept_mimetype('*/*', expect='application/json')
|
# self.assert_accept_mimetype('*/*', expect='application/json')
|
||||||
#
|
#
|
||||||
# def dont_test_invalid_accept_header_returns_406(self):
|
# def dont_test_invalid_accept_header_returns_406(self):
|
||||||
# """Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk."""
|
# """Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk."""
|
||||||
# resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT='invalid/invalid')
|
# resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT='invalid/invalid')
|
||||||
# self.assertNotEquals(resp['content-type'], 'invalid/invalid')
|
# self.assertNotEquals(resp['content-type'], 'invalid/invalid')
|
||||||
# self.assertEquals(resp.status_code, 406)
|
# self.assertEquals(resp.status_code, 406)
|
||||||
#
|
#
|
||||||
# def dont_test_prefer_specific_over_generic(self): # This test is broken right now
|
# def dont_test_prefer_specific_over_generic(self): # This test is broken right now
|
||||||
# """More specific accept types have precedence over less specific types."""
|
# """More specific accept types have precedence over less specific types."""
|
||||||
# self.assert_accept_mimetype('application/xml, */*', expect='application/xml')
|
# self.assert_accept_mimetype('application/xml, */*', expect='application/xml')
|
||||||
# self.assert_accept_mimetype('*/*, application/xml', expect='application/xml')
|
# self.assert_accept_mimetype('*/*, application/xml', expect='application/xml')
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# class AllowedMethodsTests(TestCase):
|
# class AllowedMethodsTests(TestCase):
|
||||||
# """Basic tests to check that only allowed operations may be performed on a Resource"""
|
# """Basic tests to check that only allowed operations may be performed on a Resource"""
|
||||||
#
|
#
|
||||||
# def dont_test_reading_a_read_only_resource_is_allowed(self):
|
# def dont_test_reading_a_read_only_resource_is_allowed(self):
|
||||||
# """GET requests on a read only resource should default to a 200 (OK) response"""
|
# """GET requests on a read only resource should default to a 200 (OK) response"""
|
||||||
# resp = self.client.get(reverse(views.RootResource))
|
# resp = self.client.get(reverse(views.RootResource))
|
||||||
# self.assertEquals(resp.status_code, 200)
|
# self.assertEquals(resp.status_code, 200)
|
||||||
#
|
#
|
||||||
# def dont_test_writing_to_read_only_resource_is_not_allowed(self):
|
# def dont_test_writing_to_read_only_resource_is_not_allowed(self):
|
||||||
# """PUT requests on a read only resource should default to a 405 (method not allowed) response"""
|
# """PUT requests on a read only resource should default to a 405 (method not allowed) response"""
|
||||||
# resp = self.client.put(reverse(views.RootResource), {})
|
# resp = self.client.put(reverse(views.RootResource), {})
|
||||||
|
@ -171,7 +171,7 @@ from blogpost import models, urls
|
||||||
|
|
||||||
|
|
||||||
class TestRotation(TestCase):
|
class TestRotation(TestCase):
|
||||||
"""For the example the maximum amount of Blogposts is capped off at views.MAX_POSTS.
|
"""For the example the maximum amount of Blogposts is capped off at views.MAX_POSTS.
|
||||||
Whenever a new Blogpost is posted the oldest one should be popped."""
|
Whenever a new Blogpost is posted the oldest one should be popped."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -193,7 +193,7 @@ class TestRotation(TestCase):
|
||||||
view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
|
view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
|
||||||
view(request)
|
view(request)
|
||||||
self.assertEquals(len(models.BlogPost.objects.all()),models.MAX_POSTS)
|
self.assertEquals(len(models.BlogPost.objects.all()),models.MAX_POSTS)
|
||||||
|
|
||||||
def test_fifo_behaviour(self):
|
def test_fifo_behaviour(self):
|
||||||
'''It's fine that the Blogposts are capped off at MAX_POSTS. But we want to make sure we see FIFO behaviour.'''
|
'''It's fine that the Blogposts are capped off at MAX_POSTS. But we want to make sure we see FIFO behaviour.'''
|
||||||
for post in range(15):
|
for post in range(15):
|
||||||
|
@ -201,11 +201,10 @@ class TestRotation(TestCase):
|
||||||
request = self.factory.post('/blog-post', data=form_data)
|
request = self.factory.post('/blog-post', data=form_data)
|
||||||
view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
|
view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
|
||||||
view(request)
|
view(request)
|
||||||
request = self.factory.get('/blog-post')
|
request = self.factory.get('/blog-post')
|
||||||
view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
|
view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
|
||||||
response = view(request)
|
response = view(request)
|
||||||
response_posts = json.loads(response.content)
|
response_posts = json.loads(response.content)
|
||||||
response_titles = [d['title'] for d in response_posts]
|
response_titles = [d['title'] for d in response_posts]
|
||||||
response_titles.reverse()
|
response_titles.reverse()
|
||||||
self.assertEquals(response_titles, ['%s' % i for i in range(models.MAX_POSTS - 5, models.MAX_POSTS + 5)])
|
self.assertEquals(response_titles, ['%s' % i for i in range(models.MAX_POSTS - 5, models.MAX_POSTS + 5)])
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3
|
from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3
|
||||||
from djangorestframework.mixins import ResponseMixin
|
from djangorestframework.mixins import ResponseMixin
|
||||||
from djangorestframework.renderers import DEFAULT_RENDERERS
|
from djangorestframework.renderers import DEFAULT_RENDERERS
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
from objectstore.views import ObjectStoreRoot, StoredObject
|
from objectstore.views import ObjectStoreRoot, StoredObject
|
||||||
|
|
||||||
urlpatterns = patterns('objectstore.views',
|
urlpatterns = patterns('objectstore.views',
|
||||||
url(r'^$', ObjectStoreRoot.as_view(), name='object-store-root'),
|
url(r'^$', ObjectStoreRoot.as_view(), name='object-store-root'),
|
||||||
url(r'^(?P<key>[A-Za-z0-9_-]{1,64})/$', StoredObject.as_view(), name='stored-object'),
|
url(r'^(?P<key>[A-Za-z0-9_-]{1,64})/$', StoredObject.as_view(), name='stored-object'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ObjectStoreRoot(View):
|
||||||
ctime_sorted_basenames = [item[0] for item in sorted([(os.path.basename(path), os.path.getctime(path)) for path in filepaths],
|
ctime_sorted_basenames = [item[0] for item in sorted([(os.path.basename(path), os.path.getctime(path)) for path in filepaths],
|
||||||
key=operator.itemgetter(1), reverse=True)]
|
key=operator.itemgetter(1), reverse=True)]
|
||||||
return [reverse('stored-object', kwargs={'key':key}) for key in ctime_sorted_basenames]
|
return [reverse('stored-object', kwargs={'key':key}) for key in ctime_sorted_basenames]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""
|
"""
|
||||||
Create a new stored object, with a unique key.
|
Create a new stored object, with a unique key.
|
||||||
|
|
|
@ -6,32 +6,32 @@ class PermissionsExampleView(View):
|
||||||
"""
|
"""
|
||||||
A container view for permissions examples.
|
A container view for permissions examples.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return [{'name': 'Throttling Example', 'url': reverse('throttled-resource')},
|
return [{'name': 'Throttling Example', 'url': reverse('throttled-resource')},
|
||||||
{'name': 'Logged in example', 'url': reverse('loggedin-resource')},]
|
{'name': 'Logged in example', 'url': reverse('loggedin-resource')},]
|
||||||
|
|
||||||
|
|
||||||
class ThrottlingExampleView(View):
|
class ThrottlingExampleView(View):
|
||||||
"""
|
"""
|
||||||
A basic read-only View that has a **per-user throttle** of 10 requests per minute.
|
A basic read-only View that has a **per-user throttle** of 10 requests per minute.
|
||||||
|
|
||||||
If a user exceeds the 10 requests limit within a period of one minute, the
|
If a user exceeds the 10 requests limit within a period of one minute, the
|
||||||
throttle will be applied until 60 seconds have passed since the first request.
|
throttle will be applied until 60 seconds have passed since the first request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permissions = ( PerUserThrottling, )
|
permissions = ( PerUserThrottling, )
|
||||||
throttle = '10/min'
|
throttle = '10/min'
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
Handle GET requests.
|
Handle GET requests.
|
||||||
"""
|
"""
|
||||||
return "Successful response to GET request because throttle is not yet active."
|
return "Successful response to GET request because throttle is not yet active."
|
||||||
|
|
||||||
class LoggedInExampleView(View):
|
class LoggedInExampleView(View):
|
||||||
"""
|
"""
|
||||||
You can login with **'test', 'test'.**
|
You can login with **'test', 'test'.**
|
||||||
"""
|
"""
|
||||||
permissions = (IsAuthenticated, )
|
permissions = (IsAuthenticated, )
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
|
@ -13,7 +13,7 @@ class PygmentsForm(forms.Form):
|
||||||
|
|
||||||
code = forms.CharField(widget=forms.Textarea,
|
code = forms.CharField(widget=forms.Textarea,
|
||||||
label='Code Text',
|
label='Code Text',
|
||||||
max_length=1000000,
|
max_length=1000000,
|
||||||
help_text='(Copy and paste the code text here.)')
|
help_text='(Copy and paste the code text here.)')
|
||||||
title = forms.CharField(required=False,
|
title = forms.CharField(required=False,
|
||||||
help_text='(Optional)',
|
help_text='(Optional)',
|
||||||
|
|
|
@ -46,4 +46,3 @@ class TestPygmentsExample(TestCase):
|
||||||
self.assertEquals(locations, response_locations)
|
self.assertEquals(locations, response_locations)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,6 @@ from django.conf.urls.defaults import patterns, url
|
||||||
from pygments_api.views import PygmentsRoot, PygmentsInstance
|
from pygments_api.views import PygmentsRoot, PygmentsInstance
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', PygmentsRoot.as_view(), name='pygments-root'),
|
url(r'^$', PygmentsRoot.as_view(), name='pygments-root'),
|
||||||
url(r'^([a-zA-Z0-9-]+)/$', PygmentsInstance.as_view(), name='pygments-instance'),
|
url(r'^([a-zA-Z0-9-]+)/$', PygmentsInstance.as_view(), name='pygments-instance'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -72,10 +72,10 @@ class PygmentsRoot(View):
|
||||||
linenos = 'table' if self.CONTENT['linenos'] else False
|
linenos = 'table' if self.CONTENT['linenos'] else False
|
||||||
options = {'title': self.CONTENT['title']} if self.CONTENT['title'] else {}
|
options = {'title': self.CONTENT['title']} if self.CONTENT['title'] else {}
|
||||||
formatter = HtmlFormatter(style=self.CONTENT['style'], linenos=linenos, full=True, **options)
|
formatter = HtmlFormatter(style=self.CONTENT['style'], linenos=linenos, full=True, **options)
|
||||||
|
|
||||||
with open(pathname, 'w') as outfile:
|
with open(pathname, 'w') as outfile:
|
||||||
highlight(self.CONTENT['code'], lexer, formatter, outfile)
|
highlight(self.CONTENT['code'], lexer, formatter, outfile)
|
||||||
|
|
||||||
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
|
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
|
||||||
|
|
||||||
return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', args=[unique_id])})
|
return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', args=[unique_id])})
|
||||||
|
|
|
@ -34,7 +34,7 @@ class AnotherExampleView(View):
|
||||||
if int(num) > 2:
|
if int(num) > 2:
|
||||||
return Response(status.HTTP_404_NOT_FOUND)
|
return Response(status.HTTP_404_NOT_FOUND)
|
||||||
return "GET request to AnotherExampleResource %s" % num
|
return "GET request to AnotherExampleResource %s" % num
|
||||||
|
|
||||||
def post(self, request, num):
|
def post(self, request, num):
|
||||||
"""
|
"""
|
||||||
Handle POST requests, with form validation.
|
Handle POST requests, with form validation.
|
||||||
|
|
|
@ -8,7 +8,7 @@ from coverage import coverage
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run the tests for the examples and generate a coverage report."""
|
"""Run the tests for the examples and generate a coverage report."""
|
||||||
|
|
||||||
# Discover the list of all modules that we should test coverage for
|
# Discover the list of all modules that we should test coverage for
|
||||||
project_dir = os.path.dirname(__file__)
|
project_dir = os.path.dirname(__file__)
|
||||||
cov_files = []
|
cov_files = []
|
||||||
|
@ -18,7 +18,7 @@ def main():
|
||||||
continue
|
continue
|
||||||
cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')])
|
cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')])
|
||||||
TestRunner = get_runner(settings)
|
TestRunner = get_runner(settings)
|
||||||
|
|
||||||
cov = coverage()
|
cov = coverage()
|
||||||
cov.erase()
|
cov.erase()
|
||||||
cov.start()
|
cov.start()
|
||||||
|
|
|
@ -14,8 +14,8 @@ class Sandbox(View):
|
||||||
bash: curl -X GET http://api.django-rest-framework.org/ # (Use default renderer)
|
bash: curl -X GET http://api.django-rest-framework.org/ # (Use default renderer)
|
||||||
bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
|
bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
|
||||||
|
|
||||||
The examples provided:
|
The examples provided:
|
||||||
|
|
||||||
1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
|
1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
|
||||||
2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class.
|
2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class.
|
||||||
3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [RendererMixin](http://django-rest-framework.org/library/renderers.html).
|
3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [RendererMixin](http://django-rest-framework.org/library/renderers.html).
|
||||||
|
|
|
@ -51,7 +51,7 @@ MEDIA_ROOT = 'media/'
|
||||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||||
# trailing slash if there is a path component (optional in other cases).
|
# trailing slash if there is a path component (optional in other cases).
|
||||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||||
# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL.
|
# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL.
|
||||||
MEDIA_URL = ''
|
MEDIA_URL = ''
|
||||||
|
|
||||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||||
|
@ -90,10 +90,10 @@ TEMPLATE_DIRS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
# for loading initial data
|
# for loading initial data
|
||||||
##SERIALIZATION_MODULES = {
|
##SERIALIZATION_MODULES = {
|
||||||
# 'yml': "django.core.serializers.pyyaml"
|
# 'yml': "django.core.serializers.pyyaml"
|
||||||
|
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user