Strip trailing whitespace

Result of `find -name '*.py' -exec sed -i 's/[ \t]*$//' {} \;`
This commit is contained in:
Ewoud Kohl van Wijngaarden 2011-12-13 14:48:33 +01:00
parent 4f38e78580
commit 303a1108a7
46 changed files with 379 additions and 381 deletions

View File

@ -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):
""" """

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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):
""" """

View File

@ -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):
""" """

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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.
""" """

View File

@ -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)"""

View File

@ -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()

View File

@ -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:

View File

@ -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})

View 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()

View File

@ -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
}) })

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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))

View File

@ -5,7 +5,7 @@
#from djangorestframework.response import Response #from djangorestframework.response import Response
# #
# #
#class TestResponse(TestCase): #class TestResponse(TestCase):
# #
# # Interface tests # # Interface tests
# #

View File

@ -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.
""" """

View File

@ -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):

View File

@ -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,

View File

@ -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))

View File

@ -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"""

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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})

View File

@ -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)])

View File

@ -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

View File

@ -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'),
) )

View File

@ -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.

View File

@ -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):

View File

@ -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)',

View File

@ -46,4 +46,3 @@ class TestPygmentsExample(TestCase):
self.assertEquals(locations, response_locations) self.assertEquals(locations, response_locations)

View File

@ -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'),
) )

View File

@ -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])})

View File

@ -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.

View File

@ -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()

View File

@ -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).

View File

@ -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 = (