Massive merge

This commit is contained in:
Tom Christie 2012-02-25 18:45:17 +00:00
parent 5fd4c639d7
commit 1cde31c86d
45 changed files with 859 additions and 855 deletions

View File

@ -33,6 +33,7 @@ Camille Harang <mammique>
Paul Oswald <poswald> Paul Oswald <poswald>
Sean C. Farley <scfarley> Sean C. Farley <scfarley>
Daniel Izquierdo <izquierdo> Daniel Izquierdo <izquierdo>
Can Yavuz <tschan>
THANKS TO: THANKS TO:

View File

@ -1,3 +1,3 @@
__version__ = '0.3.3' __version__ = '0.4.0-dev'
VERSION = __version__ # synonym VERSION = __version__ # synonym

View File

@ -87,8 +87,6 @@ class UserLoggedInAuthentication(BaseAuthentication):
Returns a :obj:`User` if the request session currently has a logged in user. Returns a :obj:`User` if the request session currently has a logged in user.
Otherwise returns :const:`None`. Otherwise returns :const:`None`.
""" """
request.DATA # Make sure our generic parsing runs first
if getattr(request, 'user', None) and request.user.is_active: if getattr(request, 'user', None) and request.user.is_active:
# Enforce CSRF validation for session based authentication. # Enforce CSRF validation for session based authentication.
resp = CsrfViewMiddleware().process_view(request, None, (), {}) resp = CsrfViewMiddleware().process_view(request, None, (), {})

View File

@ -214,18 +214,15 @@ else:
REASON_NO_CSRF_COOKIE = "CSRF cookie not set." REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
REASON_BAD_TOKEN = "CSRF token missing or incorrect." REASON_BAD_TOKEN = "CSRF token missing or incorrect."
def _get_failure_view(): def _get_failure_view():
""" """
Returns the view to be used for CSRF rejections Returns the view to be used for CSRF rejections
""" """
return get_callable(settings.CSRF_FAILURE_VIEW) return get_callable(settings.CSRF_FAILURE_VIEW)
def _get_new_csrf_key(): def _get_new_csrf_key():
return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
def get_token(request): def get_token(request):
""" """
Returns the the CSRF token required for a POST form. The token is an Returns the the CSRF token required for a POST form. The token is an
@ -239,7 +236,6 @@ else:
request.META["CSRF_COOKIE_USED"] = True request.META["CSRF_COOKIE_USED"] = True
return request.META.get("CSRF_COOKIE", None) return request.META.get("CSRF_COOKIE", None)
def _sanitize_token(token): def _sanitize_token(token):
# Allow only alphanum, and ensure we return a 'str' for the sake of the post # Allow only alphanum, and ensure we return a 'str' for the sake of the post
# processing middleware. # processing middleware.
@ -432,12 +428,13 @@ try:
except ImportError: except ImportError:
yaml = None yaml = None
import unittest import unittest
try: try:
import unittest.skip import unittest.skip
except ImportError: # python < 2.7 except ImportError: # python < 2.7
from unittest import TestCase from unittest import TestCase
import functools import functools
def skip(reason): def skip(reason):
# Pasted from py27/lib/unittest/case.py # Pasted from py27/lib/unittest/case.py
@ -448,20 +445,19 @@ except ImportError: # python < 2.7
if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): if not (isinstance(test_item, type) and issubclass(test_item, TestCase)):
@functools.wraps(test_item) @functools.wraps(test_item)
def skip_wrapper(*args, **kwargs): def skip_wrapper(*args, **kwargs):
pass pass
test_item = skip_wrapper test_item = skip_wrapper
test_item.__unittest_skip__ = True test_item.__unittest_skip__ = True
test_item.__unittest_skip_why__ = reason test_item.__unittest_skip_why__ = reason
return test_item return test_item
return decorator return decorator
unittest.skip = skip unittest.skip = skip
# reverse_lazy (Django 1.4 onwards)
# xml.etree.parse only throws ParseError for python >= 2.7
try: try:
from django.core.urlresolvers import reverse_lazy from xml.etree import ParseError as ETParseError
except: except ImportError: # python < 2.7
from django.core.urlresolvers import reverse ETParseError = None
from django.utils.functional import lazy
reverse_lazy = lazy(reverse, str)

View File

@ -21,14 +21,13 @@ __all__ = (
'ResponseMixin', 'ResponseMixin',
'AuthMixin', 'AuthMixin',
'ResourceMixin', 'ResourceMixin',
# Reverse URL lookup behavior
'InstanceMixin',
# Model behavior mixins # Model behavior mixins
'ReadModelMixin', 'ReadModelMixin',
'CreateModelMixin', 'CreateModelMixin',
'UpdateModelMixin', 'UpdateModelMixin',
'DeleteModelMixin', 'DeleteModelMixin',
'ListModelMixin' 'ListModelMixin',
'PaginatorMixin'
) )
@ -39,39 +38,33 @@ class RequestMixin(object):
`Mixin` class enabling the use of :class:`request.Request` in your views. `Mixin` class enabling the use of :class:`request.Request` in your views.
""" """
parser_classes = ()
"""
The set of parsers that the view can handle.
Should be a tuple/list of classes as described in the :mod:`parsers` module.
"""
request_class = Request request_class = Request
""" """
The class to use as a wrapper for the original request object. The class to use as a wrapper for the original request object.
""" """
def get_parsers(self):
"""
Instantiates and returns the list of parsers the request will use.
"""
return [p(self) for p in self.parser_classes]
def create_request(self, request): def create_request(self, request):
""" """
Creates and returns an instance of :class:`request.Request`. Creates and returns an instance of :class:`request.Request`.
This new instance wraps the `request` passed as a parameter, and use the This new instance wraps the `request` passed as a parameter, and use
parsers set on the view. the parsers set on the view.
""" """
parsers = self.get_parsers() return self.request_class(request, parsers=self.parsers)
return self.request_class(request, parsers=parsers)
@property @property
def _parsed_media_types(self): def _parsed_media_types(self):
""" """
Returns a list of all the media types that this view can parse. Return a list of all the media types that this view can parse.
""" """
return [p.media_type for p in self.parser_classes] return [parser.media_type for parser in self.parsers]
@property
def _default_parser(self):
"""
Return the view's default parser class.
"""
return self.parsers[0]
########## ResponseMixin ########## ########## ResponseMixin ##########
@ -80,58 +73,32 @@ class ResponseMixin(object):
`Mixin` class enabling the use of :class:`response.Response` in your views. `Mixin` class enabling the use of :class:`response.Response` in your views.
""" """
renderer_classes = () renderers = ()
""" """
The set of response renderers that the view can handle. The set of response renderers that the view can handle.
Should be a tuple/list of classes as described in the :mod:`renderers` module. Should be a tuple/list of classes as described in the :mod:`renderers` module.
""" """
def get_renderers(self):
"""
Instantiates and returns the list of renderers the response will use.
"""
return [r(self) for r in self.renderer_classes]
def prepare_response(self, response):
"""
Prepares and returns `response`.
This has no effect if the response is not an instance of :class:`response.Response`.
"""
if hasattr(response, 'request') and response.request is None:
response.request = self.request
# set all the cached headers
for name, value in self.headers.items():
response[name] = value
# set the views renderers on the response
response.renderers = self.get_renderers()
return response
@property
def headers(self):
"""
Dictionary of headers to set on the response.
This is useful when the response doesn't exist yet, but you
want to memorize some headers to set on it when it will exist.
"""
if not hasattr(self, '_headers'):
self._headers = {}
return self._headers
@property @property
def _rendered_media_types(self): def _rendered_media_types(self):
""" """
Return an list of all the media types that this view can render. Return an list of all the media types that this response can render.
""" """
return [renderer.media_type for renderer in self.get_renderers()] return [renderer.media_type for renderer in self.renderers]
@property @property
def _rendered_formats(self): def _rendered_formats(self):
""" """
Return a list of all the formats that this view can render. Return a list of all the formats that this response can render.
""" """
return [renderer.format for renderer in self.get_renderers()] return [renderer.format for renderer in self.renderers]
@property
def _default_renderer(self):
"""
Return the response's default renderer class.
"""
return self.renderers[0]
########## Auth Mixin ########## ########## Auth Mixin ##########
@ -254,30 +221,6 @@ class ResourceMixin(object):
else: else:
return None return None
##########
class InstanceMixin(object):
"""
`Mixin` class that is used to identify a `View` class as being the canonical identifier
for the resources it is mapped to.
"""
@classmethod
def as_view(cls, **initkwargs):
"""
Store the callable object on the resource class that has been associated with this view.
"""
view = super(InstanceMixin, cls).as_view(**initkwargs)
resource = getattr(cls(**initkwargs), 'resource', None)
if resource:
# We do a little dance when we store the view callable...
# we need to store it wrapped in a 1-tuple, so that inspect will treat it
# as a function when we later look it up (rather than turning it into a method).
# This makes sure our URL reversing works ok.
resource.view_callable = (view,)
return view
########## Model Mixins ########## ########## Model Mixins ##########
@ -411,7 +354,7 @@ class CreateModelMixin(ModelMixin):
response = Response(instance, status=status.HTTP_201_CREATED) response = Response(instance, status=status.HTTP_201_CREATED)
# Set headers # Set headers
if hasattr(instance, 'get_absolute_url'): if hasattr(self.resource, 'url'):
response['Location'] = self.resource(self).url(instance) response['Location'] = self.resource(self).url(instance)
return response return response

View File

@ -20,6 +20,8 @@ from djangorestframework.compat import yaml
from djangorestframework.response import ImmediateResponse from djangorestframework.response import ImmediateResponse
from djangorestframework.utils.mediatypes import media_type_matches from djangorestframework.utils.mediatypes import media_type_matches
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
from djangorestframework.compat import ETParseError
from xml.parsers.expat import ExpatError
import datetime import datetime
import decimal import decimal
@ -43,13 +45,6 @@ class BaseParser(object):
media_type = None media_type = None
def __init__(self, view=None):
"""
Initialize the parser with the ``View`` instance as state,
in case the parser needs to access any metadata on the :obj:`View` object.
"""
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*.
@ -63,12 +58,12 @@ class BaseParser(object):
""" """
return media_type_matches(self.media_type, content_type) return media_type_matches(self.media_type, content_type)
def parse(self, stream): def parse(self, stream, meta, upload_handlers):
""" """
Given a *stream* to read from, return the deserialized output. Given a *stream* to read from, return the deserialized output.
Should return a 2-tuple of (data, files). Should return a 2-tuple of (data, files).
""" """
raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.") raise NotImplementedError(".parse() Must be overridden to be implemented.")
class JSONParser(BaseParser): class JSONParser(BaseParser):
@ -78,7 +73,7 @@ class JSONParser(BaseParser):
media_type = 'application/json' media_type = 'application/json'
def parse(self, stream): def parse(self, stream, meta, upload_handlers):
""" """
Returns a 2-tuple of `(data, files)`. Returns a 2-tuple of `(data, files)`.
@ -93,29 +88,26 @@ class JSONParser(BaseParser):
status=status.HTTP_400_BAD_REQUEST) status=status.HTTP_400_BAD_REQUEST)
if yaml: class YAMLParser(BaseParser):
class YAMLParser(BaseParser): """
Parses YAML-serialized data.
"""
media_type = 'application/yaml'
def parse(self, stream, meta, upload_handlers):
""" """
Parses YAML-serialized data. Returns a 2-tuple of `(data, files)`.
`data` will be an object which is the parsed content of the response.
`files` will always be `None`.
""" """
try:
media_type = 'application/yaml' return (yaml.safe_load(stream), None)
except ValueError, exc:
def parse(self, stream): raise ImmediateResponse(
""" {'detail': 'YAML parse error - %s' % unicode(exc)},
Returns a 2-tuple of `(data, files)`. status=status.HTTP_400_BAD_REQUEST)
`data` will be an object which is the parsed content of the response.
`files` will always be `None`.
"""
try:
return (yaml.safe_load(stream), None)
except ValueError, exc:
raise ImmediateResponse(
{'detail': 'YAML parse error - %s' % unicode(exc)},
status=status.HTTP_400_BAD_REQUEST)
else:
YAMLParser = None
class PlainTextParser(BaseParser): class PlainTextParser(BaseParser):
@ -125,7 +117,7 @@ class PlainTextParser(BaseParser):
media_type = 'text/plain' media_type = 'text/plain'
def parse(self, stream): def parse(self, stream, meta, upload_handlers):
""" """
Returns a 2-tuple of `(data, files)`. Returns a 2-tuple of `(data, files)`.
@ -142,7 +134,7 @@ class FormParser(BaseParser):
media_type = 'application/x-www-form-urlencoded' media_type = 'application/x-www-form-urlencoded'
def parse(self, stream): def parse(self, stream, meta, upload_handlers):
""" """
Returns a 2-tuple of `(data, files)`. Returns a 2-tuple of `(data, files)`.
@ -160,21 +152,20 @@ class MultiPartParser(BaseParser):
media_type = 'multipart/form-data' media_type = 'multipart/form-data'
def parse(self, stream): def parse(self, stream, meta, upload_handlers):
""" """
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.
""" """
upload_handlers = self.view.request._get_upload_handlers()
try: try:
django_parser = DjangoMultiPartParser(self.view.request.META, stream, upload_handlers) parser = DjangoMultiPartParser(meta, stream, upload_handlers)
return parser.parse()
except MultiPartParserError, exc: except MultiPartParserError, exc:
raise ImmediateResponse( raise ImmediateResponse(
{'detail': 'multipart parse error - %s' % unicode(exc)}, {'detail': 'multipart parse error - %s' % unicode(exc)},
status=status.HTTP_400_BAD_REQUEST) status=status.HTTP_400_BAD_REQUEST)
return django_parser.parse()
class XMLParser(BaseParser): class XMLParser(BaseParser):
@ -184,14 +175,18 @@ class XMLParser(BaseParser):
media_type = 'application/xml' media_type = 'application/xml'
def parse(self, stream): def parse(self, stream, meta, upload_handlers):
""" """
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`.
""" """
tree = ET.parse(stream) try:
tree = ET.parse(stream)
except (ExpatError, ETParseError, ValueError), exc:
content = {'detail': 'XML parse error - %s' % unicode(exc)}
raise ImmediateResponse(content, status=status.HTTP_400_BAD_REQUEST)
data = self._xml_convert(tree.getroot()) data = self._xml_convert(tree.getroot())
return (data, None) return (data, None)
@ -251,5 +246,7 @@ DEFAULT_PARSERS = (
XMLParser XMLParser
) )
if YAMLParser: if yaml:
DEFAULT_PARSERS += (YAMLParser,) DEFAULT_PARSERS += (YAMLParser, )
else:
YAMLParser = None

View File

@ -6,20 +6,18 @@ by serializing the output along with documentation regarding the View, output st
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.core.serializers.json import DateTimeAwareJSONEncoder from django.core.serializers.json import DateTimeAwareJSONEncoder
from django.template import RequestContext, loader from django.template import RequestContext, loader
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework.compat import yaml from djangorestframework.compat import yaml
from djangorestframework.utils import dict2xml, url_resolves, allowed_methods from djangorestframework.utils import dict2xml
from djangorestframework.utils.breadcrumbs import get_breadcrumbs from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
from djangorestframework import VERSION from djangorestframework import VERSION
import string import string
from urllib import quote_plus
__all__ = ( __all__ = (
'BaseRenderer', 'BaseRenderer',
@ -156,25 +154,22 @@ class XMLRenderer(BaseRenderer):
return dict2xml(obj) return dict2xml(obj)
if yaml: class YAMLRenderer(BaseRenderer):
class YAMLRenderer(BaseRenderer): """
Renderer which serializes to YAML.
"""
media_type = 'application/yaml'
format = 'yaml'
def render(self, obj=None, media_type=None):
""" """
Renderer which serializes to YAML. Renders *obj* into serialized YAML.
""" """
if obj is None:
return ''
media_type = 'application/yaml' return yaml.safe_dump(obj)
format = 'yaml'
def render(self, obj=None, media_type=None):
"""
Renders *obj* into serialized YAML.
"""
if obj is None:
return ''
return yaml.safe_dump(obj)
else:
YAMLRenderer = None
class TemplateRenderer(BaseRenderer): class TemplateRenderer(BaseRenderer):
@ -218,8 +213,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
""" """
# Find the first valid renderer and render the content. (Don't use another documenting renderer.) # Find the first valid renderer and render the content. (Don't use another documenting renderer.)
renderers = [renderer for renderer in view.renderer_classes renderers = [renderer for renderer in view.renderers
if not issubclass(renderer, DocumentingTemplateRenderer)] if not issubclass(renderer, DocumentingTemplateRenderer)]
if not renderers: if not renderers:
return '[No renderers were found]' return '[No renderers were found]'
@ -278,14 +273,14 @@ class DocumentingTemplateRenderer(BaseRenderer):
# NB. http://jacobian.org/writing/dynamic-form-generation/ # NB. http://jacobian.org/writing/dynamic-form-generation/
class GenericContentForm(forms.Form): class GenericContentForm(forms.Form):
def __init__(self, request): def __init__(self, view, request):
"""We don't know the names of the fields we want to set until the point the form is instantiated, """We don't know the names of the fields we want to set until the point the form is instantiated,
as they are determined by the Resource the form is being created against. as they are determined by the Resource the form is being created against.
Add the fields dynamically.""" Add the fields dynamically."""
super(GenericContentForm, self).__init__() super(GenericContentForm, self).__init__()
contenttype_choices = [(media_type, media_type) for media_type in request._parsed_media_types] contenttype_choices = [(media_type, media_type) for media_type in view._parsed_media_types]
initial_contenttype = request._default_parser.media_type initial_contenttype = view._default_parser.media_type
self.fields[request._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type', self.fields[request._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type',
choices=contenttype_choices, choices=contenttype_choices,
@ -298,7 +293,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
return None return None
# Okey doke, let's do it # Okey doke, let's do it
return GenericContentForm(view.request) return GenericContentForm(view, view.request)
def get_name(self): def get_name(self):
try: try:
@ -327,13 +322,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
put_form_instance = self._get_form_instance(self.view, 'put') put_form_instance = self._get_form_instance(self.view, 'put')
post_form_instance = self._get_form_instance(self.view, 'post') post_form_instance = self._get_form_instance(self.view, 'post')
if url_resolves(settings.LOGIN_URL) and url_resolves(settings.LOGOUT_URL):
login_url = "%s?next=%s" % (settings.LOGIN_URL, quote_plus(self.view.request.path))
logout_url = "%s?next=%s" % (settings.LOGOUT_URL, quote_plus(self.view.request.path))
else:
login_url = None
logout_url = None
name = self.get_name() name = self.get_name()
description = self.get_description() description = self.get_description()
@ -343,21 +331,18 @@ class DocumentingTemplateRenderer(BaseRenderer):
context = RequestContext(self.view.request, { context = RequestContext(self.view.request, {
'content': content, 'content': content,
'view': self.view, 'view': self.view,
'request': self.view.request, # TODO: remove 'request': self.view.request,
'response': self.view.response, 'response': self.view.response,
'description': description, 'description': description,
'name': name, 'name': name,
'version': VERSION, 'version': VERSION,
'breadcrumblist': breadcrumb_list, 'breadcrumblist': breadcrumb_list,
'allowed_methods': allowed_methods(self.view), 'allowed_methods': self.view.allowed_methods,
'available_formats': self.view._rendered_formats, 'available_formats': self.view._rendered_formats,
'put_form': put_form_instance, 'put_form': put_form_instance,
'post_form': post_form_instance, 'post_form': post_form_instance,
'login_url': login_url,
'logout_url': logout_url,
'FORMAT_PARAM': self._FORMAT_QUERY_PARAM, 'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
'METHOD_PARAM': getattr(self.view.request, '_METHOD_PARAM', None), 'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
'ADMIN_MEDIA_PREFIX': getattr(settings, 'ADMIN_MEDIA_PREFIX', None),
}) })
ret = template.render(context) ret = template.render(context)
@ -415,5 +400,7 @@ DEFAULT_RENDERERS = (
XMLRenderer XMLRenderer
) )
if YAMLRenderer: if yaml:
DEFAULT_RENDERERS += (YAMLRenderer,) DEFAULT_RENDERERS += (YAMLRenderer, )
else:
YAMLRenderer = None

View File

@ -4,15 +4,14 @@ object received in all the views.
The wrapped request then offers a richer API, in particular : The wrapped request then offers a richer API, in particular :
- content automatically parsed according to `Content-Type` header, and available as :meth:`.DATA<Request.DATA>` - content automatically parsed according to `Content-Type` header,
and available as :meth:`.DATA<Request.DATA>`
- full support of PUT method, including support for file uploads - full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content - form overloading of HTTP method, content type and content
""" """
from djangorestframework.response import ImmediateResponse
from djangorestframework import status from djangorestframework import status
from djangorestframework.utils.mediatypes import is_form_media_type from djangorestframework.utils.mediatypes import is_form_media_type
from djangorestframework.utils import as_tuple
from StringIO import StringIO from StringIO import StringIO
@ -20,6 +19,14 @@ from StringIO import StringIO
__all__ = ('Request',) __all__ = ('Request',)
class Empty:
pass
def _hasattr(obj, name):
return not getattr(obj, name) is Empty
class Request(object): class Request(object):
""" """
Wrapper allowing to enhance a standard `HttpRequest` instance. Wrapper allowing to enhance a standard `HttpRequest` instance.
@ -35,19 +42,29 @@ class Request(object):
_CONTENT_PARAM = '_content' _CONTENT_PARAM = '_content'
def __init__(self, request=None, parsers=None): def __init__(self, request=None, parsers=None):
self.request = request self._request = request
if parsers is not None: self.parsers = parsers or ()
self.parsers = parsers self._data = Empty
self._files = Empty
self._method = Empty
self._content_type = Empty
self._stream = Empty
def get_parsers(self):
"""
Instantiates and returns the list of parsers the request will use.
"""
return [parser() for parser in self.parsers]
@property @property
def method(self): def method(self):
""" """
Returns the HTTP method. Returns the HTTP method.
This allows the `method` to be overridden by using a hidden `form` field This allows the `method` to be overridden by using a hidden `form`
on a form POST request. field on a form POST request.
""" """
if not hasattr(self, '_method'): if not _hasattr(self, '_method'):
self._load_method_and_content_type() self._load_method_and_content_type()
return self._method return self._method
@ -60,10 +77,19 @@ class Request(object):
as it allows the content type to be overridden by using a hidden form as it allows the content type to be overridden by using a hidden form
field on a form POST request. field on a form POST request.
""" """
if not hasattr(self, '_content_type'): if not _hasattr(self, '_content_type'):
self._load_method_and_content_type() self._load_method_and_content_type()
return self._content_type return self._content_type
@property
def stream(self):
"""
Returns an object that may be used to stream the request content.
"""
if not _hasattr(self, '_stream'):
self._load_stream()
return self._stream
@property @property
def DATA(self): def DATA(self):
""" """
@ -72,7 +98,7 @@ class Request(object):
Similar to ``request.POST``, except that it handles arbitrary parsers, Similar to ``request.POST``, except that it handles arbitrary parsers,
and also works on methods other than POST (eg PUT). and also works on methods other than POST (eg PUT).
""" """
if not hasattr(self, '_data'): if not _hasattr(self, '_data'):
self._load_data_and_files() self._load_data_and_files()
return self._data return self._data
@ -83,7 +109,7 @@ class Request(object):
Similar to ``request.FILES``, except that it handles arbitrary parsers, Similar to ``request.FILES``, except that it handles arbitrary parsers,
and also works on methods other than POST (eg PUT). and also works on methods other than POST (eg PUT).
""" """
if not hasattr(self, '_files'): if not _hasattr(self, '_files'):
self._load_data_and_files() self._load_data_and_files()
return self._files return self._files
@ -91,11 +117,11 @@ class Request(object):
""" """
Parses the request content into self.DATA and self.FILES. Parses the request content into self.DATA and self.FILES.
""" """
if not hasattr(self, '_content_type'): if not _hasattr(self, '_content_type'):
self._load_method_and_content_type() self._load_method_and_content_type()
if not hasattr(self, '_data'): if not _hasattr(self, '_data'):
(self._data, self._files) = self._parse(self._get_stream(), self._content_type) (self._data, self._files) = self._parse()
def _load_method_and_content_type(self): def _load_method_and_content_type(self):
""" """
@ -104,100 +130,83 @@ class Request(object):
self._content_type = self.META.get('HTTP_CONTENT_TYPE', self.META.get('CONTENT_TYPE', '')) self._content_type = self.META.get('HTTP_CONTENT_TYPE', self.META.get('CONTENT_TYPE', ''))
self._perform_form_overloading() self._perform_form_overloading()
# if the HTTP method was not overloaded, we take the raw HTTP method # if the HTTP method was not overloaded, we take the raw HTTP method
if not hasattr(self, '_method'): if not _hasattr(self, '_method'):
self._method = self.request.method self._method = self._request.method
def _get_stream(self):
"""
Returns an object that may be used to stream the request content.
"""
def _load_stream(self):
try: try:
content_length = int(self.META.get('CONTENT_LENGTH', self.META.get('HTTP_CONTENT_LENGTH'))) content_length = int(self.META.get('CONTENT_LENGTH',
self.META.get('HTTP_CONTENT_LENGTH')))
except (ValueError, TypeError): except (ValueError, TypeError):
content_length = 0 content_length = 0
# TODO: Add 1.3's LimitedStream to compat and use that.
# NOTE: Currently only supports parsing request body as a stream with 1.3
if content_length == 0: if content_length == 0:
return None self._stream = None
elif hasattr(self, 'read'): elif hasattr(self._request, 'read'):
return self self._stream = self._request
return StringIO(self.raw_post_data) else:
self._stream = StringIO(self.raw_post_data)
def _perform_form_overloading(self): def _perform_form_overloading(self):
""" """
If this is a form POST request, then we need to check if the method and content/content_type have been If this is a form POST request, then we need to check if the method and
overridden by setting them in hidden form fields or not. content/content_type have been overridden by setting them in hidden
form fields or not.
""" """
# We only need to use form overloading on form POST requests. # We only need to use form overloading on form POST requests.
if (not self._USE_FORM_OVERLOADING or self.request.method != 'POST' if (not self._USE_FORM_OVERLOADING
or not is_form_media_type(self._content_type)): or self._request.method != 'POST'
or not is_form_media_type(self._content_type)):
return return
# At this point we're committed to parsing the request as form data. # At this point we're committed to parsing the request as form data.
self._data = data = self.POST.copy() self._data = self._request.POST
self._files = self.FILES self._files = self._request.FILES
# Method overloading - change the method and remove the param from the content. # Method overloading - change the method and remove the param from the content.
if self._METHOD_PARAM in data: if self._METHOD_PARAM in self._data:
# NOTE: unlike `get`, `pop` on a `QueryDict` seems to return a list of values. # NOTE: `pop` on a `QueryDict` returns a list of values.
self._method = self._data.pop(self._METHOD_PARAM)[0].upper() self._method = self._data.pop(self._METHOD_PARAM)[0].upper()
# Content overloading - modify the content type, and re-parse. # Content overloading - modify the content type, and re-parse.
if self._CONTENT_PARAM in data and self._CONTENTTYPE_PARAM in data: if (self._CONTENT_PARAM in self._data and
self._CONTENTTYPE_PARAM in self._data):
self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0] self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0]
stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0]) self._stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
(self._data, self._files) = self._parse(stream, self._content_type) (self._data, self._files) = self._parse()
def _parse(self, stream, content_type): def _parse(self):
""" """
Parse the request content. Parse the request content.
May raise a 415 ImmediateResponse (Unsupported Media Type), or a 400 ImmediateResponse (Bad Request). May raise a 415 ImmediateResponse (Unsupported Media Type), or a
400 ImmediateResponse (Bad Request).
""" """
if stream is None or content_type is None: if self.stream is None or self.content_type is None:
return (None, None) return (None, None)
for parser in as_tuple(self.parsers): for parser in self.get_parsers():
if parser.can_handle_request(content_type): if parser.can_handle_request(self.content_type):
return parser.parse(stream) return parser.parse(self.stream, self.META, self.upload_handlers)
raise ImmediateResponse({ self._raise_415_response(self._content_type)
'error': 'Unsupported media type in request \'%s\'.' % content_type},
status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
@property def _raise_415_response(self, content_type):
def _parsed_media_types(self):
""" """
Return a list of all the media types that this view can parse. Raise a 415 response if we cannot parse the given content type.
""" """
return [parser.media_type for parser in self.parsers] from djangorestframework.response import ImmediateResponse
@property raise ImmediateResponse(
def _default_parser(self): {
""" 'error': 'Unsupported media type in request \'%s\'.'
Return the view's default parser class. % content_type
""" },
return self.parsers[0] status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
def _get_parsers(self):
if hasattr(self, '_parsers'):
return self._parsers
return ()
def _set_parsers(self, value):
self._parsers = value
parsers = property(_get_parsers, _set_parsers)
def __getattr__(self, name): def __getattr__(self, name):
""" """
When an attribute is not present on the calling instance, try to get it Proxy other attributes to the underlying HttpRequest object.
from the original request.
""" """
if hasattr(self.request, name): return getattr(self._request, name)
return getattr(self.request, name)
else:
return super(Request, self).__getattribute__(name)

View File

@ -1,16 +1,13 @@
from django import forms from django import forms
from django.core.urlresolvers import get_urlconf, get_resolver, NoReverseMatch
from django.db import models
from djangorestframework.response import ImmediateResponse from djangorestframework.response import ImmediateResponse
from djangorestframework.reverse import reverse from djangorestframework.serializer import Serializer
from djangorestframework.serializer import Serializer, _SkipField from djangorestframework.utils import as_tuple
from djangorestframework.utils import as_tuple, reverse
class BaseResource(Serializer): class BaseResource(Serializer):
""" """
Base class for all Resource classes, which simply defines the interface they provide. Base class for all Resource classes, which simply defines the interface
they provide.
""" """
fields = None fields = None
include = None include = None
@ -19,11 +16,13 @@ class BaseResource(Serializer):
def __init__(self, view=None, depth=None, stack=[], **kwargs): def __init__(self, view=None, depth=None, stack=[], **kwargs):
super(BaseResource, self).__init__(depth, stack, **kwargs) super(BaseResource, self).__init__(depth, stack, **kwargs)
self.view = view self.view = view
self.request = getattr(view, 'request', None)
def validate_request(self, data, files=None): def validate_request(self, data, files=None):
""" """
Given the request content return the cleaned, validated content. Given the request content return the cleaned, validated content.
Typically raises a :exc:`response.ImmediateResponse` with status code 400 (Bad Request) on failure. Typically raises a :exc:`response.ImmediateResponse` with status code
400 (Bad Request) on failure.
""" """
return data return data
@ -37,7 +36,8 @@ class BaseResource(Serializer):
class Resource(BaseResource): 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.
@ -220,9 +220,6 @@ 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
#__metaclass__ = _RegisterModelResource
form = None form = None
""" """
The form class that should be used for request validation. The form class that should be used for request validation.
@ -256,7 +253,7 @@ class ModelResource(FormResource):
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 = ()
""" """
The list of extra fields to include. This is only used if :attr:`fields` is not set. The list of extra fields to include. This is only used if :attr:`fields` is not set.
""" """
@ -319,47 +316,6 @@ class ModelResource(FormResource):
return form() return form()
def url(self, instance):
"""
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.
This method can be overridden if you need to set the resource url reversing explicitly.
"""
if not hasattr(self, 'view_callable'):
raise _SkipField
# dis does teh magicks...
urlconf = get_urlconf()
resolver = get_resolver(urlconf)
possibilities = resolver.reverse_dict.getlist(self.view_callable[0])
for tuple_item in possibilities:
possibility = tuple_item[0]
# pattern = tuple_item[1]
# Note: defaults = tuple_item[2] for django >= 1.3
for result, params in possibility:
#instance_attrs = dict([ (param, getattr(instance, param)) for param in params if hasattr(instance, param) ])
instance_attrs = {}
for param in params:
if not hasattr(instance, param):
continue
attr = getattr(instance, param)
if isinstance(attr, models.Model):
instance_attrs[param] = attr.pk
else:
instance_attrs[param] = attr
try:
return reverse(self.view_callable[0], self.view.request, kwargs=instance_attrs)
except NoReverseMatch:
pass
raise _SkipField
@property @property
def _model_fields_set(self): def _model_fields_set(self):
""" """

View File

@ -27,6 +27,10 @@ from djangorestframework import status
__all__ = ('Response', 'ImmediateResponse') __all__ = ('Response', 'ImmediateResponse')
class NotAcceptable(Exception):
pass
class Response(SimpleTemplateResponse): class Response(SimpleTemplateResponse):
""" """
An HttpResponse that may include content that hasn't yet been serialized. An HttpResponse that may include content that hasn't yet been serialized.
@ -40,25 +44,30 @@ class Response(SimpleTemplateResponse):
_ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params _ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
_IGNORE_IE_ACCEPT_HEADER = True _IGNORE_IE_ACCEPT_HEADER = True
def __init__(self, content=None, status=None, request=None, renderers=None, headers=None): def __init__(self, content=None, status=None, headers=None, view=None, request=None, renderers=None):
# First argument taken by `SimpleTemplateResponse.__init__` is template_name, # First argument taken by `SimpleTemplateResponse.__init__` is template_name,
# which we don't need # which we don't need
super(Response, self).__init__(None, status=status) super(Response, self).__init__(None, status=status)
# We need to store our content in raw content to avoid overriding HttpResponse's
# `content` property
self.raw_content = content self.raw_content = content
self.has_content_body = content is not None self.has_content_body = content is not None
self.request = request
self.headers = headers and headers[:] or [] self.headers = headers and headers[:] or []
if renderers is not None: self.view = view
self.renderers = renderers self.request = request
self.renderers = renderers
def get_renderers(self):
"""
Instantiates and returns the list of renderers the response will use.
"""
return [renderer(self.view) for renderer in self.renderers]
@property @property
def rendered_content(self): def rendered_content(self):
""" """
The final rendered content. Accessing this attribute triggers the complete rendering cycle : The final rendered content. Accessing this attribute triggers the
selecting suitable renderer, setting response's actual content type, rendering data. complete rendering cycle: selecting suitable renderer, setting
response's actual content type, rendering data.
""" """
renderer, media_type = self._determine_renderer() renderer, media_type = self._determine_renderer()
@ -70,6 +79,13 @@ class Response(SimpleTemplateResponse):
return renderer.render(self.raw_content, media_type) return renderer.render(self.raw_content, media_type)
return renderer.render() return renderer.render()
def render(self):
try:
return super(Response, self).render()
except NotAcceptable:
response = self._get_406_response()
return response.render()
@property @property
def status_text(self): def status_text(self):
""" """
@ -88,8 +104,6 @@ class Response(SimpleTemplateResponse):
If those are useless, a default value is returned instead. If those are useless, a default value is returned instead.
""" """
request = self.request request = self.request
if request is None:
return ['*/*']
if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None): if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
# Use _accept parameter override # Use _accept parameter override
@ -108,70 +122,52 @@ class Response(SimpleTemplateResponse):
def _determine_renderer(self): def _determine_renderer(self):
""" """
Determines the appropriate renderer for the output, given the list of accepted media types, Determines the appropriate renderer for the output, given the list of
and the :attr:`renderers` set on this class. accepted media types, and the :attr:`renderers` set on this class.
Returns a 2-tuple of `(renderer, media_type)` Returns a 2-tuple of `(renderer, media_type)`
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html See: RFC 2616, Section 14
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
""" """
renderers = self.get_renderers()
accepts = self._determine_accept_list()
# Not acceptable response - Ignore accept header.
if self.status_code == 406:
return (renderers[0], renderers[0].media_type)
# Check the acceptable media types against each renderer, # Check the acceptable media types against each renderer,
# attempting more specific media types first # attempting more specific media types first
# NB. The inner loop here isn't as bad as it first looks :) # NB. The inner loop here isn't as bad as it first looks :)
# Worst case is we're looping over len(accept_list) * len(self.renderers) # Worst case is we're looping over len(accept_list) * len(self.renderers)
for media_type_list in order_by_precedence(self._determine_accept_list()): for media_type_list in order_by_precedence(accepts):
for renderer in self.renderers: for renderer in renderers:
for media_type in media_type_list: for media_type in media_type_list:
if renderer.can_handle_response(media_type): if renderer.can_handle_response(media_type):
return renderer, media_type return renderer, media_type
# No acceptable renderers were found # No acceptable renderers were found
raise ImmediateResponse({'detail': 'Could not satisfy the client\'s Accept header', raise NotAcceptable
'available_types': self._rendered_media_types},
status=status.HTTP_406_NOT_ACCEPTABLE,
renderers=self.renderers)
def _get_renderers(self): def _get_406_response(self):
if hasattr(self, '_renderers'): renderer = self.renderers[0]
return self._renderers return Response(
return () {
'detail': 'Could not satisfy the client\'s Accept header',
def _set_renderers(self, value): 'available_types': [renderer.media_type
self._renderers = value for renderer in self.renderers]
},
renderers = property(_get_renderers, _set_renderers) status=status.HTTP_406_NOT_ACCEPTABLE,
view=self.view, request=self.request, renderers=[renderer])
@property
def _rendered_media_types(self):
"""
Return an list of all the media types that this response can render.
"""
return [renderer.media_type for renderer in self.renderers]
@property
def _rendered_formats(self):
"""
Return a list of all the formats that this response can render.
"""
return [renderer.format for renderer in self.renderers]
@property
def _default_renderer(self):
"""
Return the response's default renderer class.
"""
return self.renderers[0]
class ImmediateResponse(Response, Exception): class ImmediateResponse(Response, Exception):
""" """
A subclass of :class:`Response` used to abort the current request handling. An exception representing an Response that should be returned immediately.
Any content should be serialized as-is, without being filtered.
""" """
def __str__(self): def __init__(self, *args, **kwargs):
""" self.response = Response(*args, **kwargs)
Since this class is also an exception it has to provide a sensible
representation for the cases when it is treated as an exception.
"""
return ('%s must be caught in try/except block, '
'and returned as a normal HttpResponse' % self.__class__.__name__)

View File

@ -2,22 +2,19 @@
Provide reverse functions that return fully qualified URLs Provide reverse functions that return fully qualified URLs
""" """
from django.core.urlresolvers import reverse as django_reverse from django.core.urlresolvers import reverse as django_reverse
from djangorestframework.compat import reverse_lazy as django_reverse_lazy from django.utils.functional import lazy
def reverse(viewname, request, *args, **kwargs): def reverse(viewname, *args, **kwargs):
""" """
Do the same as `django.core.urlresolvers.reverse` but using Same as `django.core.urlresolvers.reverse`, but optionally takes a request
*request* to build a fully qualified URL. and returns a fully qualified URL, using the request to get the base URL.
""" """
request = kwargs.pop('request', None)
url = django_reverse(viewname, *args, **kwargs) url = django_reverse(viewname, *args, **kwargs)
return request.build_absolute_uri(url) if request:
return request.build_absolute_uri(url)
return url
def reverse_lazy(viewname, request, *args, **kwargs): reverse_lazy = lazy(reverse, str)
"""
Do the same as `django.core.urlresolvers.reverse_lazy` but using
*request* to build a fully qualified URL.
"""
url = django_reverse_lazy(viewname, *args, **kwargs)
return request.build_absolute_uri(url)

View File

@ -53,11 +53,6 @@ MEDIA_ROOT = ''
# Examples: "http://media.lawrence.com", "http://example.com/media/" # Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = '' MEDIA_URL = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/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.
SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy' SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy'

View File

@ -20,8 +20,15 @@
<h1 id="site-name">{% block branding %}<a href='http://django-rest-framework.org'>Django REST framework</a> <span class="version"> v {{ version }}</span>{% endblock %}</h1> <h1 id="site-name">{% block branding %}<a href='http://django-rest-framework.org'>Django REST framework</a> <span class="version"> v {{ version }}</span>{% endblock %}</h1>
</div> </div>
<div id="user-tools"> <div id="user-tools">
{% if user.is_active %}Welcome, {{ user }}.{% if logout_url %} <a href='{{ logout_url }}'>Log out</a>{% endif %}{% else %}Anonymous {% if login_url %}<a href='{{ login_url }}'>Log in</a>{% endif %}{% endif %} {% block userlinks %}
{% block userlinks %}{% endblock %} {% if user.is_active %}
Welcome, {{ user }}.
<a href='{% url djangorestframework:logout %}?next={{ request.path }}'>Log out</a>
{% else %}
Anonymous
<a href='{% url djangorestframework:login %}?next={{ request.path }}'>Log in</a>
{% endif %}
{% endblock %}
</div> </div>
{% block nav-global %}{% endblock %} {% block nav-global %}{% endblock %}
</div> </div>

View File

@ -17,7 +17,7 @@
<div id="content" class="colM"> <div id="content" class="colM">
<div id="content-main"> <div id="content-main">
<form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form"> <form method="post" action="{% url djangorestframework:login %}" id="login-form">
{% csrf_token %} {% csrf_token %}
<div class="form-row"> <div class="form-row">
<label for="id_username">Username:</label> {{ form.username }} <label for="id_username">Username:</label> {{ form.username }}

View File

@ -10,4 +10,3 @@ for module in modules:
exec("from djangorestframework.tests.%s import __doc__ as module_doc" % module) exec("from djangorestframework.tests.%s import __doc__ as module_doc" % module)
exec("from djangorestframework.tests.%s import *" % module) exec("from djangorestframework.tests.%s import *" % module)
__test__[module] = module_doc or "" __test__[module] = module_doc or ""

View File

@ -1,3 +1,4 @@
from django.conf.urls.defaults import patterns, url, include
from django.test import TestCase from django.test import TestCase
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
@ -15,9 +16,19 @@ SAFARI_5_0_USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-ca) AppleWebKit/5
OPERA_11_0_MSIE_USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00' OPERA_11_0_MSIE_USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00'
OPERA_11_0_OPERA_USER_AGENT = 'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00' OPERA_11_0_OPERA_USER_AGENT = 'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00'
urlpatterns = patterns('',
url(r'^api', include('djangorestframework.urls', namespace='djangorestframework'))
)
class UserAgentMungingTest(TestCase): class UserAgentMungingTest(TestCase):
"""We need to fake up the accept headers when we deal with MSIE. Blergh. """
http://www.gethifi.com/blog/browser-rest-http-accept-headers""" We need to fake up the accept headers when we deal with MSIE. Blergh.
http://www.gethifi.com/blog/browser-rest-http-accept-headers
"""
urls = 'djangorestframework.tests.accept'
def setUp(self): def setUp(self):

View File

@ -1,5 +1,4 @@
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django.test import TestCase
from django.forms import ModelForm from django.forms import ModelForm
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from djangorestframework.resources import ModelResource from djangorestframework.resources import ModelResource
@ -7,18 +6,22 @@ from djangorestframework.views import ListOrCreateModelView, InstanceModelView
from djangorestframework.tests.models import CustomUser from djangorestframework.tests.models import CustomUser
from djangorestframework.tests.testcases import TestModelsTestCase from djangorestframework.tests.testcases import TestModelsTestCase
class GroupResource(ModelResource): class GroupResource(ModelResource):
model = Group model = Group
class UserForm(ModelForm): class UserForm(ModelForm):
class Meta: class Meta:
model = User model = User
exclude = ('last_login', 'date_joined') exclude = ('last_login', 'date_joined')
class UserResource(ModelResource): class UserResource(ModelResource):
model = User model = User
form = UserForm form = UserForm
class CustomUserResource(ModelResource): class CustomUserResource(ModelResource):
model = CustomUser model = CustomUser

View File

@ -27,7 +27,7 @@ else:
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'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
) )

View File

@ -132,17 +132,18 @@
# self.assertEqual(files['file1'].read(), 'blablabla') # self.assertEqual(files['file1'].read(), 'blablabla')
from StringIO import StringIO from StringIO import StringIO
from cgi import parse_qs
from django import forms from django import forms
from django.test import TestCase from django.test import TestCase
from djangorestframework.parsers import FormParser from djangorestframework.parsers import FormParser
from djangorestframework.parsers import XMLParser from djangorestframework.parsers import XMLParser
import datetime import datetime
class Form(forms.Form): class Form(forms.Form):
field1 = forms.CharField(max_length=3) field1 = forms.CharField(max_length=3)
field2 = forms.CharField() field2 = forms.CharField()
class TestFormParser(TestCase): class TestFormParser(TestCase):
def setUp(self): def setUp(self):
self.string = "field1=abc&field2=defghijk" self.string = "field1=abc&field2=defghijk"
@ -152,10 +153,11 @@ class TestFormParser(TestCase):
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(
@ -163,13 +165,13 @@ 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)
} }
@ -183,21 +185,21 @@ class TestXMLParser(TestCase):
'</sub_data_list>' '</sub_data_list>'
'<name>name</name>' '<name>name</name>'
'</root>' '</root>'
) )
self._complex_data = { self._complex_data = {
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00), "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
"name": "name", "name": "name",
"sub_data_list": [ "sub_data_list": [
{ {
"sub_id": 1, "sub_id": 1,
"sub_name": "first" "sub_name": "first"
}, },
{ {
"sub_id": 2, "sub_id": 2,
"sub_name": "second" "sub_name": "second"
} }
] ]
} }
def test_parse(self): def test_parse(self):
parser = XMLParser(None) parser = XMLParser(None)

View File

@ -1,21 +1,180 @@
import re import re
from django.conf.urls.defaults import patterns, url, include
from django.test import TestCase from django.test import TestCase
from django.conf.urls.defaults import patterns, url from djangorestframework import status
from django.test import TestCase from djangorestframework.compat import View as DjangoView
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.mixins import ResponseMixin
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
from djangorestframework.parsers import JSONParser, YAMLParser, XMLParser from djangorestframework.parsers import YAMLParser, XMLParser
from StringIO import StringIO from StringIO import StringIO
import datetime import datetime
from decimal import Decimal from decimal import Decimal
DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
class RendererA(BaseRenderer):
media_type = 'mock/renderera'
format = "formata"
def render(self, obj=None, media_type=None):
return RENDERER_A_SERIALIZER(obj)
class RendererB(BaseRenderer):
media_type = 'mock/rendererb'
format = "formatb"
def render(self, obj=None, media_type=None):
return RENDERER_B_SERIALIZER(obj)
class MockView(ResponseMixin, DjangoView):
renderers = (RendererA, RendererB)
def get(self, request, **kwargs):
response = Response(DUMMYSTATUS, DUMMYCONTENT)
return self.render(response)
class MockGETView(View):
def get(self, request, **kwargs):
return {'foo': ['bar', 'baz']}
class HTMLView(View):
renderers = (DocumentingHTMLRenderer, )
def get(self, request, **kwargs):
return 'text'
class HTMLView1(View):
renderers = (DocumentingHTMLRenderer, JSONRenderer)
def get(self, request, **kwargs):
return 'text'
urlpatterns = patterns('',
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
url(r'^html$', HTMLView.as_view()),
url(r'^html1$', HTMLView1.as_view()),
url(r'^api', include('djangorestframework.urls', namespace='djangorestframework'))
)
class RendererIntegrationTests(TestCase):
"""
End-to-end testing of renderers using an RendererMixin on a generic view.
"""
urls = 'djangorestframework.tests.renderers'
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
resp = self.client.get('/')
self.assertEquals(resp['Content-Type'], RendererA.media_type)
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_head_method_serializes_no_content(self):
"""No response must be included in HEAD requests."""
resp = self.client.head('/')
self.assertEquals(resp.status_code, DUMMYSTATUS)
self.assertEquals(resp['Content-Type'], RendererA.media_type)
self.assertEquals(resp.content, '')
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
resp = self.client.get('/', HTTP_ACCEPT='*/*')
self.assertEquals(resp['Content-Type'], RendererA.media_type)
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_renderer_serializes_content_default_case(self):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for the default renderer)"""
resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
self.assertEquals(resp['Content-Type'], RendererA.media_type)
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_renderer_serializes_content_non_default_case(self):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for a non-default renderer)"""
resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_renderer_serializes_content_on_accept_query(self):
"""The '_accept' query string should behave in the same way as the Accept header."""
resp = self.client.get('/?_accept=%s' % RendererB.media_type)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
def test_specified_renderer_serializes_content_on_format_query(self):
"""If a 'format' query is specified, the renderer with the matching
format attribute should serialize the response."""
resp = self.client.get('/?format=%s' % RendererB.format)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_renderer_serializes_content_on_format_kwargs(self):
"""If a 'format' keyword arg is specified, the renderer with the matching
format attribute should serialize the response."""
resp = self.client.get('/something.formatb')
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_renderer_is_used_on_format_query_with_matching_accept(self):
"""If both a 'format' query and a matching Accept header specified,
the renderer with the matching format attribute should serialize the response."""
resp = self.client.get('/?format=%s' % RendererB.format,
HTTP_ACCEPT=RendererB.media_type)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_conflicting_format_query_and_accept_ignores_accept(self):
"""If a 'format' query is specified that does not match the Accept
header, we should only honor the 'format' query string."""
resp = self.client.get('/?format=%s' % RendererB.format,
HTTP_ACCEPT='dummy')
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_bla(self): # What the f***?
resp = self.client.get('/?format=formatb',
HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
_flat_repr = '{"foo": ["bar", "baz"]}' _flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}' _indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
@ -27,6 +186,7 @@ def strip_trailing_whitespace(content):
""" """
return re.sub(' +\n', '\n', content) return re.sub(' +\n', '\n', content)
class JSONRendererTests(TestCase): class JSONRendererTests(TestCase):
""" """
Tests specific to the JSON Renderer Tests specific to the JSON Renderer
@ -51,30 +211,16 @@ class JSONRendererTests(TestCase):
content = renderer.render(obj, 'application/json; indent=2') content = renderer.render(obj, 'application/json; indent=2')
self.assertEquals(strip_trailing_whitespace(content), _indented_repr) self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
def test_render_and_parse(self):
"""
Test rendering and then parsing returns the original object.
IE obj -> render -> parse -> obj.
"""
obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer(None)
parser = JSONParser(None)
content = renderer.render(obj, 'application/json')
(data, files) = parser.parse(StringIO(content))
self.assertEquals(obj, data)
class MockGETView(View): class MockGETView(View):
def get(self, request, **kwargs): def get(self, request, *args, **kwargs):
return Response({'foo': ['bar', 'baz']}) return Response({'foo': ['bar', 'baz']})
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])), url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])), url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
) )
@ -149,22 +295,21 @@ if YAMLRenderer:
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
""" """
_complex_data = { _complex_data = {
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00), "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
"name": "name", "name": "name",
"sub_data_list": [ "sub_data_list": [
{ {
"sub_id": 1, "sub_id": 1,
"sub_name": "first" "sub_name": "first"
}, },
{ {
"sub_id": 2, "sub_id": 2,
"sub_name": "second" "sub_name": "second"
} }
] ]
@ -219,12 +364,12 @@ class XMLRendererTestCase(TestCase):
renderer = XMLRenderer(None) renderer = XMLRenderer(None)
content = renderer.render({'field': None}, 'application/xml') content = renderer.render({'field': None}, 'application/xml')
self.assertXMLContains(content, '<field></field>') self.assertXMLContains(content, '<field></field>')
def test_render_complex_data(self): def test_render_complex_data(self):
""" """
Test XML rendering. Test XML rendering.
""" """
renderer = XMLRenderer(None) renderer = XMLRenderer(None)
content = renderer.render(self._complex_data, 'application/xml') content = renderer.render(self._complex_data, 'application/xml')
self.assertXMLContains(content, '<sub_name>first</sub_name>') self.assertXMLContains(content, '<sub_name>first</sub_name>')
self.assertXMLContains(content, '<sub_name>second</sub_name>') self.assertXMLContains(content, '<sub_name>second</sub_name>')
@ -233,9 +378,9 @@ class XMLRendererTestCase(TestCase):
""" """
Test XML rendering. Test XML rendering.
""" """
renderer = XMLRenderer(None) renderer = XMLRenderer(None)
content = StringIO(renderer.render(self._complex_data, 'application/xml')) content = StringIO(renderer.render(self._complex_data, 'application/xml'))
parser = XMLParser(None) parser = XMLParser(None)
complex_data_out, dummy = parser.parse(content) complex_data_out, dummy = parser.parse(content)
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out)) error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
@ -245,4 +390,3 @@ class XMLRendererTestCase(TestCase):
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

@ -4,205 +4,214 @@ Tests for content parsing, and form-overloaded content parsing.
from django.conf.urls.defaults import patterns from django.conf.urls.defaults import patterns
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase, Client from django.test import TestCase, Client
from django.utils import simplejson as json
from djangorestframework import status from djangorestframework import status
from djangorestframework.authentication import UserLoggedInAuthentication from djangorestframework.authentication import UserLoggedInAuthentication
from djangorestframework.compat import RequestFactory from djangorestframework.utils import RequestFactory
from djangorestframework.mixins import RequestMixin from djangorestframework.parsers import (
from djangorestframework.parsers import FormParser, MultiPartParser, \ FormParser,
PlainTextParser, JSONParser MultiPartParser,
PlainTextParser,
JSONParser
)
from djangorestframework.request import Request from djangorestframework.request import Request
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.request import Request
from djangorestframework.views import View from djangorestframework.views import View
class RequestTestCase(TestCase): factory = RequestFactory()
def build_request(self, method, *args, **kwargs):
factory = RequestFactory()
method = getattr(factory, method)
original_request = method(*args, **kwargs)
return Request(original_request)
class TestMethodOverloading(RequestTestCase): class TestMethodOverloading(TestCase):
def test_GET_method(self):
def test_standard_behaviour_determines_GET(self): """
"""GET requests identified""" GET requests identified.
request = self.build_request('get', '/') """
request = factory.get('/')
self.assertEqual(request.method, 'GET') self.assertEqual(request.method, 'GET')
def test_standard_behaviour_determines_POST(self): def test_POST_method(self):
"""POST requests identified""" """
request = self.build_request('post', '/') POST requests identified.
"""
request = factory.post('/')
self.assertEqual(request.method, 'POST') self.assertEqual(request.method, 'POST')
def test_overloaded_POST_behaviour_determines_overloaded_method(self): def test_HEAD_method(self):
"""POST requests can be overloaded to another method by setting a reserved form field""" """
request = self.build_request('post', '/', {Request._METHOD_PARAM: 'DELETE'}) HEAD requests identified.
self.assertEqual(request.method, 'DELETE') """
request = factory.head('/')
def test_HEAD_is_a_valid_method(self):
"""HEAD requests identified"""
request = request = self.build_request('head', '/')
self.assertEqual(request.method, 'HEAD') self.assertEqual(request.method, 'HEAD')
def test_overloaded_method(self):
"""
POST requests can be overloaded to another method by setting a
reserved form field
"""
request = factory.post('/', {Request._METHOD_PARAM: 'DELETE'})
self.assertEqual(request.method, 'DELETE')
class TestContentParsing(RequestTestCase):
def build_request(self, method, *args, **kwargs): class TestContentParsing(TestCase):
factory = RequestFactory()
parsers = kwargs.pop('parsers', None)
method = getattr(factory, method)
original_request = method(*args, **kwargs)
rkwargs = {}
if parsers is not None:
rkwargs['parsers'] = parsers
request = Request(original_request, **rkwargs)
# TODO: Just a hack because the parsers need a view. This will be fixed in the future
class Obj(object): pass
obj = Obj()
obj.request = request
for p in request.parsers:
p.view = obj
return request
def test_standard_behaviour_determines_no_content_GET(self): def test_standard_behaviour_determines_no_content_GET(self):
"""Ensure request.DATA returns None for GET request with no content.""" """
request = self.build_request('get', '/') Ensure request.DATA returns None for GET request with no content.
"""
request = factory.get('/')
self.assertEqual(request.DATA, None) self.assertEqual(request.DATA, None)
def test_standard_behaviour_determines_no_content_HEAD(self): def test_standard_behaviour_determines_no_content_HEAD(self):
"""Ensure request.DATA returns None for HEAD request.""" """
request = self.build_request('head', '/') Ensure request.DATA returns None for HEAD request.
"""
request = factory.head('/')
self.assertEqual(request.DATA, None) self.assertEqual(request.DATA, None)
def test_standard_behaviour_determines_form_content_POST(self): def test_standard_behaviour_determines_form_content_POST(self):
"""Ensure request.DATA returns content for POST request with form content.""" """
form_data = {'qwerty': 'uiop'} Ensure request.DATA returns content for POST request with form content.
parsers = (FormParser(), MultiPartParser()) """
data = {'qwerty': 'uiop'}
request = self.build_request('post', '/', data=form_data, parsers=parsers) parsers = (FormParser, MultiPartParser)
self.assertEqual(request.DATA.items(), form_data.items()) request = factory.post('/', data, parser=parsers)
self.assertEqual(request.DATA.items(), data.items())
def test_standard_behaviour_determines_non_form_content_POST(self): def test_standard_behaviour_determines_non_form_content_POST(self):
"""Ensure request.DATA returns content for POST request with non-form content.""" """
Ensure request.DATA returns content for POST request with
non-form content.
"""
content = 'qwerty' content = 'qwerty'
content_type = 'text/plain' content_type = 'text/plain'
parsers = (PlainTextParser(),) parsers = (PlainTextParser,)
request = factory.post('/', content, content_type=content_type,
request = self.build_request('post', '/', content, content_type=content_type, parsers=parsers) parsers=parsers)
self.assertEqual(request.DATA, content) self.assertEqual(request.DATA, content)
def test_standard_behaviour_determines_form_content_PUT(self): def test_standard_behaviour_determines_form_content_PUT(self):
"""Ensure request.DATA returns content for PUT request with form content.""" """
form_data = {'qwerty': 'uiop'} Ensure request.DATA returns content for PUT request with form content.
parsers = (FormParser(), MultiPartParser()) """
data = {'qwerty': 'uiop'}
request = self.build_request('put', '/', data=form_data, parsers=parsers) parsers = (FormParser, MultiPartParser)
self.assertEqual(request.DATA.items(), form_data.items()) request = factory.put('/', data, parsers=parsers)
self.assertEqual(request.DATA.items(), data.items())
def test_standard_behaviour_determines_non_form_content_PUT(self): def test_standard_behaviour_determines_non_form_content_PUT(self):
"""Ensure request.DATA returns content for PUT request with non-form content.""" """
Ensure request.DATA returns content for PUT request with
non-form content.
"""
content = 'qwerty' content = 'qwerty'
content_type = 'text/plain' content_type = 'text/plain'
parsers = (PlainTextParser(),) parsers = (PlainTextParser, )
request = factory.put('/', content, content_type=content_type,
request = self.build_request('put', '/', content, content_type=content_type, parsers=parsers) parsers=parsers)
self.assertEqual(request.DATA, content) self.assertEqual(request.DATA, content)
def test_overloaded_behaviour_allows_content_tunnelling(self): def test_overloaded_behaviour_allows_content_tunnelling(self):
"""Ensure request.DATA returns content for overloaded POST request""" """
Ensure request.DATA returns content for overloaded POST request.
"""
content = 'qwerty' content = 'qwerty'
content_type = 'text/plain' content_type = 'text/plain'
form_data = {Request._CONTENT_PARAM: content, data = {
Request._CONTENTTYPE_PARAM: content_type} Request._CONTENT_PARAM: content,
parsers = (PlainTextParser(),) Request._CONTENTTYPE_PARAM: content_type
}
request = self.build_request('post', '/', form_data, parsers=parsers) parsers = (PlainTextParser, )
request = factory.post('/', data, parsers=parsers)
self.assertEqual(request.DATA, content) self.assertEqual(request.DATA, content)
def test_accessing_post_after_data_form(self): def test_accessing_post_after_data_form(self):
"""Ensures request.POST can be accessed after request.DATA in form request""" """
form_data = {'qwerty': 'uiop'} Ensures request.POST can be accessed after request.DATA in
parsers = (FormParser(), MultiPartParser()) form request.
"""
request = self.build_request('post', '/', data=form_data) data = {'qwerty': 'uiop'}
self.assertEqual(request.DATA.items(), form_data.items()) request = factory.post('/', data=data)
self.assertEqual(request.POST.items(), form_data.items()) self.assertEqual(request.DATA.items(), data.items())
self.assertEqual(request.POST.items(), data.items())
def test_accessing_post_after_data_for_json(self): def test_accessing_post_after_data_for_json(self):
"""Ensures request.POST can be accessed after request.DATA in json request""" """
from django.utils import simplejson as json Ensures request.POST can be accessed after request.DATA in
json request.
"""
data = {'qwerty': 'uiop'} data = {'qwerty': 'uiop'}
content = json.dumps(data) content = json.dumps(data)
content_type = 'application/json' content_type = 'application/json'
parsers = (JSONParser(),) parsers = (JSONParser, )
request = self.build_request('post', '/', content, content_type=content_type, parsers=parsers) request = factory.post('/', content, content_type=content_type,
parsers=parsers)
self.assertEqual(request.DATA.items(), data.items()) self.assertEqual(request.DATA.items(), data.items())
self.assertEqual(request.POST.items(), []) self.assertEqual(request.POST.items(), [])
def test_accessing_post_after_data_for_overloaded_json(self): def test_accessing_post_after_data_for_overloaded_json(self):
"""Ensures request.POST can be accessed after request.DATA in overloaded json request""" """
from django.utils import simplejson as json Ensures request.POST can be accessed after request.DATA in overloaded
json request.
"""
data = {'qwerty': 'uiop'} data = {'qwerty': 'uiop'}
content = json.dumps(data) content = json.dumps(data)
content_type = 'application/json' content_type = 'application/json'
parsers = (JSONParser(),) parsers = (JSONParser, )
form_data = {Request._CONTENT_PARAM: content, form_data = {Request._CONTENT_PARAM: content,
Request._CONTENTTYPE_PARAM: content_type} Request._CONTENTTYPE_PARAM: content_type}
request = self.build_request('post', '/', data=form_data, parsers=parsers) request = factory.post('/', form_data, parsers=parsers)
self.assertEqual(request.DATA.items(), data.items()) self.assertEqual(request.DATA.items(), data.items())
self.assertEqual(request.POST.items(), form_data.items()) self.assertEqual(request.POST.items(), form_data.items())
def test_accessing_data_after_post_form(self): def test_accessing_data_after_post_form(self):
"""Ensures request.DATA can be accessed after request.POST in form request""" """
form_data = {'qwerty': 'uiop'} Ensures request.DATA can be accessed after request.POST in
form request.
"""
data = {'qwerty': 'uiop'}
parsers = (FormParser, MultiPartParser) parsers = (FormParser, MultiPartParser)
request = self.build_request('post', '/', data=form_data, parsers=parsers) request = factory.post('/', data, parsers=parsers)
self.assertEqual(request.POST.items(), form_data.items()) self.assertEqual(request.POST.items(), data.items())
self.assertEqual(request.DATA.items(), form_data.items()) self.assertEqual(request.DATA.items(), data.items())
def test_accessing_data_after_post_for_json(self): def test_accessing_data_after_post_for_json(self):
"""Ensures request.DATA can be accessed after request.POST in json request""" """
from django.utils import simplejson as json Ensures request.DATA can be accessed after request.POST in
json request.
"""
data = {'qwerty': 'uiop'} data = {'qwerty': 'uiop'}
content = json.dumps(data) content = json.dumps(data)
content_type = 'application/json' content_type = 'application/json'
parsers = (JSONParser(),) parsers = (JSONParser, )
request = factory.post('/', content, content_type=content_type,
request = self.build_request('post', '/', content, content_type=content_type, parsers=parsers) parsers=parsers)
post_items = request.POST.items() self.assertEqual(request.POST.items(), [])
self.assertEqual(len(post_items), 1)
self.assertEqual(len(post_items[0]), 2)
self.assertEqual(post_items[0][0], content)
self.assertEqual(request.DATA.items(), data.items()) self.assertEqual(request.DATA.items(), data.items())
def test_accessing_data_after_post_for_overloaded_json(self): def test_accessing_data_after_post_for_overloaded_json(self):
"""Ensures request.DATA can be accessed after request.POST in overloaded json request""" """
from django.utils import simplejson as json Ensures request.DATA can be accessed after request.POST in overloaded
json request
"""
data = {'qwerty': 'uiop'} data = {'qwerty': 'uiop'}
content = json.dumps(data) content = json.dumps(data)
content_type = 'application/json' content_type = 'application/json'
parsers = (JSONParser(),) parsers = (JSONParser, )
form_data = {Request._CONTENT_PARAM: content, form_data = {Request._CONTENT_PARAM: content,
Request._CONTENTTYPE_PARAM: content_type} Request._CONTENTTYPE_PARAM: content_type}
request = self.build_request('post', '/', data=form_data, parsers=parsers) request = factory.post('/', form_data, parsers=parsers)
self.assertEqual(request.POST.items(), form_data.items()) self.assertEqual(request.POST.items(), form_data.items())
self.assertEqual(request.DATA.items(), data.items()) self.assertEqual(request.DATA.items(), data.items())
class MockView(View): class MockView(View):
authentication = (UserLoggedInAuthentication,) authentication = (UserLoggedInAuthentication,)
def post(self, request): def post(self, request):
if request.POST.get('example') is not None: if request.POST.get('example') is not None:
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
@ -223,17 +232,19 @@ class TestContentParsingWithAuthentication(TestCase):
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)
self.req = RequestFactory()
def test_user_logged_in_authentication_has_post_when_not_logged_in(self): def test_user_logged_in_authentication_has_POST_when_not_logged_in(self):
"""Ensures request.POST exists after UserLoggedInAuthentication when user doesn't log in""" """
Ensures request.POST exists after UserLoggedInAuthentication when user
doesn't log in.
"""
content = {'example': 'example'} content = {'example': 'example'}
response = self.client.post('/', content) response = self.client.post('/', content)
self.assertEqual(status.HTTP_200_OK, response.status_code, "POST data is malformed") self.assertEqual(status.HTTP_200_OK, response.status_code)
response = self.csrf_client.post('/', content) response = self.csrf_client.post('/', content)
self.assertEqual(status.HTTP_200_OK, response.status_code, "POST data is malformed") self.assertEqual(status.HTTP_200_OK, response.status_code)
# def test_user_logged_in_authentication_has_post_when_logged_in(self): # def test_user_logged_in_authentication_has_post_when_logged_in(self):
# """Ensures request.POST exists after UserLoggedInAuthentication when user does log in""" # """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""

View File

@ -1,18 +1,19 @@
import json import json
import unittest import unittest
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url, include
from django.test import TestCase from django.test import TestCase
from djangorestframework.response import Response, ImmediateResponse from djangorestframework.response import Response, ImmediateResponse
from djangorestframework.mixins import ResponseMixin
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.compat import View as DjangoView
from djangorestframework.renderers import BaseRenderer, DEFAULT_RENDERERS
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework import status from djangorestframework import status
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ from djangorestframework.renderers import (
XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer BaseRenderer,
JSONRenderer,
DocumentingHTMLRenderer,
DEFAULT_RENDERERS
)
class TestResponseDetermineRenderer(TestCase): class TestResponseDetermineRenderer(TestCase):
@ -20,7 +21,7 @@ class TestResponseDetermineRenderer(TestCase):
def get_response(self, url='', accept_list=[], renderers=[]): def get_response(self, url='', accept_list=[], renderers=[]):
kwargs = {} kwargs = {}
if accept_list is not None: if accept_list is not None:
kwargs['HTTP_ACCEPT'] = HTTP_ACCEPT=','.join(accept_list) kwargs['HTTP_ACCEPT'] = ','.join(accept_list)
request = RequestFactory().get(url, **kwargs) request = RequestFactory().get(url, **kwargs)
return Response(request=request, renderers=renderers) return Response(request=request, renderers=renderers)
@ -43,7 +44,7 @@ class TestResponseDetermineRenderer(TestCase):
""" """
response = self.get_response(accept_list=None) response = self.get_response(accept_list=None)
self.assertEqual(response._determine_accept_list(), ['*/*']) self.assertEqual(response._determine_accept_list(), ['*/*'])
def test_determine_accept_list_overriden_header(self): def test_determine_accept_list_overriden_header(self):
""" """
Test Accept header overriding. Test Accept header overriding.
@ -81,7 +82,7 @@ class TestResponseDetermineRenderer(TestCase):
renderer, media_type = response._determine_renderer() renderer, media_type = response._determine_renderer()
self.assertEqual(media_type, '*/*') self.assertEqual(media_type, '*/*')
self.assertTrue(renderer, prenderer) self.assertTrue(renderer, prenderer)
def test_determine_renderer_no_renderer(self): def test_determine_renderer_no_renderer(self):
""" """
Test determine renderer when no renderer can satisfy the Accept list. Test determine renderer when no renderer can satisfy the Accept list.
@ -94,14 +95,14 @@ class TestResponseDetermineRenderer(TestCase):
class TestResponseRenderContent(TestCase): class TestResponseRenderContent(TestCase):
def get_response(self, url='', accept_list=[], content=None): def get_response(self, url='', accept_list=[], content=None):
request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list)) request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
return Response(request=request, content=content, renderers=[r() for r in DEFAULT_RENDERERS]) return Response(request=request, content=content, renderers=[r() for r in DEFAULT_RENDERERS])
def test_render(self): def test_render(self):
""" """
Test rendering simple data to json. Test rendering simple data to json.
""" """
content = {'a': 1, 'b': [1, 2, 3]} content = {'a': 1, 'b': [1, 2, 3]}
content_type = 'application/json' content_type = 'application/json'
@ -134,34 +135,33 @@ class RendererB(BaseRenderer):
return RENDERER_B_SERIALIZER(obj) return RENDERER_B_SERIALIZER(obj)
class MockView(ResponseMixin, DjangoView): class MockView(View):
renderer_classes = (RendererA, RendererB) renderers = (RendererA, RendererB)
def get(self, request, **kwargs): def get(self, request, **kwargs):
response = Response(DUMMYCONTENT, status=DUMMYSTATUS) return Response(DUMMYCONTENT, status=DUMMYSTATUS)
self.response = self.prepare_response(response)
return self.response
class HTMLView(View): class HTMLView(View):
renderer_classes = (DocumentingHTMLRenderer, ) renderers = (DocumentingHTMLRenderer, )
def get(self, request, **kwargs): def get(self, request, **kwargs):
return Response('text') return Response('text')
class HTMLView1(View): class HTMLView1(View):
renderer_classes = (DocumentingHTMLRenderer, JSONRenderer) renderers = (DocumentingHTMLRenderer, JSONRenderer)
def get(self, request, **kwargs): def get(self, request, **kwargs):
return Response('text') return Response('text')
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])), url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
url(r'^html$', HTMLView.as_view()), url(r'^html$', HTMLView.as_view()),
url(r'^html1$', HTMLView1.as_view()), url(r'^html1$', HTMLView1.as_view()),
url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
) )
@ -257,13 +257,6 @@ class RendererIntegrationTests(TestCase):
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_bla(self):
resp = self.client.get('/?format=formatb',
HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
class Issue122Tests(TestCase): class Issue122Tests(TestCase):
""" """
@ -275,10 +268,10 @@ class Issue122Tests(TestCase):
""" """
Test if no infinite recursion occurs. Test if no infinite recursion occurs.
""" """
resp = self.client.get('/html') self.client.get('/html')
def test_html_renderer_is_first(self): def test_html_renderer_is_first(self):
""" """
Test if no infinite recursion occurs. Test if no infinite recursion occurs.
""" """
resp = self.client.get('/html1') self.client.get('/html1')

View File

@ -16,7 +16,8 @@ class MyView(View):
renderers = (JSONRenderer, ) renderers = (JSONRenderer, )
def get(self, request): def get(self, request):
return Response(reverse('another', request)) return Response(reverse('myview', request=request))
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^myview$', MyView.as_view(), name='myview'), url(r'^myview$', MyView.as_view(), name='myview'),

View File

@ -1,16 +1,17 @@
from django.conf.urls.defaults import patterns, url from django.core.urlresolvers import reverse
from django.conf.urls.defaults import patterns, url, include
from django.http import HttpResponse from django.http import HttpResponse
from django.test import TestCase from django.test import TestCase
from django.test import Client
from django import forms from django import forms
from django.db import models from django.db import models
from django.utils import simplejson as json
from djangorestframework.views import View
from djangorestframework.parsers import JSONParser
from djangorestframework.resources import ModelResource from djangorestframework.resources import ModelResource
from djangorestframework.views import ListOrCreateModelView, InstanceModelView from djangorestframework.views import (
View,
from StringIO import StringIO ListOrCreateModelView,
InstanceModelView
)
class MockView(View): class MockView(View):
@ -24,6 +25,7 @@ class MockViewFinal(View):
def final(self, request, response, *args, **kwargs): def final(self, request, response, *args, **kwargs):
return HttpResponse('{"test": "passed"}', content_type="application/json") return HttpResponse('{"test": "passed"}', content_type="application/json")
class ResourceMockView(View): class ResourceMockView(View):
"""This is a resource-based mock view""" """This is a resource-based mock view"""
@ -34,6 +36,7 @@ class ResourceMockView(View):
form = MockForm form = MockForm
class MockResource(ModelResource): class MockResource(ModelResource):
"""This is a mock model-based resource""" """This is a mock model-based resource"""
@ -45,16 +48,16 @@ class MockResource(ModelResource):
model = MockResourceModel model = MockResourceModel
fields = ('foo', 'bar', 'baz') fields = ('foo', 'bar', 'baz')
urlpatterns = patterns('djangorestframework.utils.staticviews', urlpatterns = patterns('',
url(r'^accounts/login$', 'api_login'),
url(r'^accounts/logout$', 'api_logout'),
url(r'^mock/$', MockView.as_view()), url(r'^mock/$', MockView.as_view()),
url(r'^mock/final/$', MockViewFinal.as_view()), url(r'^mock/final/$', MockViewFinal.as_view()),
url(r'^resourcemock/$', ResourceMockView.as_view()), url(r'^resourcemock/$', ResourceMockView.as_view()),
url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)), url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)),
url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)), url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)),
url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
) )
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'
@ -62,8 +65,7 @@ class BaseViewTests(TestCase):
def test_view_call_final(self): def test_view_call_final(self):
response = self.client.options('/mock/final/') response = self.client.options('/mock/final/')
self.assertEqual(response['Content-Type'].split(';')[0], "application/json") self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
parser = JSONParser(None) data = json.loads(response.content)
(data, files) = parser.parse(StringIO(response.content))
self.assertEqual(data['test'], 'passed') self.assertEqual(data['test'], 'passed')
def test_options_method_simple_view(self): def test_options_method_simple_view(self):
@ -77,9 +79,9 @@ class BaseViewTests(TestCase):
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):
@ -87,9 +89,9 @@ class BaseViewTests(TestCase):
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):
@ -97,17 +99,16 @@ class BaseViewTests(TestCase):
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) data = json.loads(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'])
@ -123,15 +124,12 @@ class ExtraViewsTests(TestCase):
def test_login_view(self): def test_login_view(self):
"""Ensure the login view exists""" """Ensure the login view exists"""
response = self.client.get('/accounts/login') response = self.client.get(reverse('djangorestframework:login'))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'].split(';')[0], 'text/html') self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
def test_logout_view(self): def test_logout_view(self):
"""Ensure the logout view exists""" """Ensure the logout view exists"""
response = self.client.get('/accounts/logout') response = self.client.get(reverse('djangorestframework:logout'))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'].split(';')[0], 'text/html') self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
# TODO: Add login/logout behaviour tests

View File

@ -1,6 +1,9 @@
from django.conf.urls.defaults import patterns from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('djangorestframework.utils.staticviews',
(r'^accounts/login/$', 'api_login'), template_name = {'template_name': 'djangorestframework/login.html'}
(r'^accounts/logout/$', 'api_logout'),
urlpatterns = patterns('django.contrib.auth.views',
url(r'^login/$', 'login', template_name, name='login'),
url(r'^logout/$', 'logout', template_name, name='logout'),
) )

View File

@ -1,24 +1,18 @@
import django
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.utils.xmlutils import SimplerXMLGenerator from django.utils.xmlutils import SimplerXMLGenerator
from django.core.urlresolvers import resolve, reverse as django_reverse from django.core.urlresolvers import resolve
from django.conf import settings
from djangorestframework.compat import StringIO from djangorestframework.compat import StringIO
from djangorestframework.compat import RequestFactory as DjangoRequestFactory
from djangorestframework.request import Request
import re import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
#def admin_media_prefix(request):
# """Adds the ADMIN_MEDIA_PREFIX to the request context."""
# return {'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX}
from mediatypes import media_type_matches, is_form_media_type
from mediatypes import add_media_type_param, get_media_type_params, order_by_precedence
MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )') MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
def as_tuple(obj): def as_tuple(obj):
""" """
Given an object which may be a list/tuple, another object, or None, Given an object which may be a list/tuple, another object, or None,
@ -49,45 +43,6 @@ def url_resolves(url):
return True return True
def allowed_methods(view):
"""
Return the list of uppercased allowed HTTP methods on `view`.
"""
return [method.upper() for method in view.http_method_names if hasattr(view, method)]
# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
#class object_dict(dict):
# """object view of dict, you can
# >>> a = object_dict()
# >>> a.fish = 'fish'
# >>> a['fish']
# 'fish'
# >>> a['water'] = 'water'
# >>> a.water
# 'water'
# >>> a.test = {'value': 1}
# >>> a.test2 = object_dict({'name': 'test2', 'value': 2})
# >>> a.test, a.test2.name, a.test2.value
# (1, 'test2', 2)
# """
# def __init__(self, initd=None):
# if initd is None:
# initd = {}
# dict.__init__(self, initd)
#
# def __getattr__(self, item):
# d = self.__getitem__(item)
# # if value is the only key in object, you can omit it
# if isinstance(d, dict) and 'value' in d and len(d) == 1:
# return d['value']
# else:
# return d
#
# def __setattr__(self, item, value):
# self.__setitem__(item, value)
# From xml2dict # From xml2dict
class XML2Dict(object): class XML2Dict(object):
@ -99,24 +54,23 @@ class XML2Dict(object):
# Save attrs and text, hope there will not be a child with same name # Save attrs and text, hope there will not be a child with same name
if node.text: if node.text:
node_tree = node.text node_tree = node.text
for (k,v) in node.attrib.items(): for (k, v) in node.attrib.items():
k,v = self._namespace_split(k, v) k, v = self._namespace_split(k, v)
node_tree[k] = v node_tree[k] = v
#Save childrens #Save childrens
for child in node.getchildren(): for child in node.getchildren():
tag, tree = self._namespace_split(child.tag, self._parse_node(child)) tag, tree = self._namespace_split(child.tag, self._parse_node(child))
if tag not in node_tree: # the first time, so store it in dict if tag not in node_tree: # the first time, so store it in dict
node_tree[tag] = tree node_tree[tag] = tree
continue continue
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
def _namespace_split(self, tag, value): def _namespace_split(self, tag, value):
""" """
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients' Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
@ -179,23 +133,41 @@ class XMLRenderer():
xml.endDocument() xml.endDocument()
return stream.getvalue() return stream.getvalue()
def dict2xml(input): def dict2xml(input):
return XMLRenderer().dict2xml(input) return XMLRenderer().dict2xml(input)
def reverse(viewname, request, *args, **kwargs): class RequestFactory(DjangoRequestFactory):
""" """
Do the same as :py:func:`django.core.urlresolvers.reverse` but using Replicate RequestFactory, but return Request, not HttpRequest.
*request* to build a fully qualified URL.
""" """
return request.build_absolute_uri(django_reverse(viewname, *args, **kwargs)) def get(self, *args, **kwargs):
parsers = kwargs.pop('parsers', None)
request = super(RequestFactory, self).get(*args, **kwargs)
return Request(request, parsers)
if django.VERSION >= (1, 4): def post(self, *args, **kwargs):
from django.core.urlresolvers import reverse_lazy as django_reverse_lazy parsers = kwargs.pop('parsers', None)
request = super(RequestFactory, self).post(*args, **kwargs)
return Request(request, parsers)
def reverse_lazy(viewname, request, *args, **kwargs): def put(self, *args, **kwargs):
""" parsers = kwargs.pop('parsers', None)
Do the same as :py:func:`django.core.urlresolvers.reverse_lazy` but using request = super(RequestFactory, self).put(*args, **kwargs)
*request* to build a fully qualified URL. return Request(request, parsers)
"""
return request.build_absolute_uri(django_reverse_lazy(viewname, *args, **kwargs)) def delete(self, *args, **kwargs):
parsers = kwargs.pop('parsers', None)
request = super(RequestFactory, self).delete(*args, **kwargs)
return Request(request, parsers)
def head(self, *args, **kwargs):
parsers = kwargs.pop('parsers', None)
request = super(RequestFactory, self).head(*args, **kwargs)
return Request(request, parsers)
def options(self, *args, **kwargs):
parsers = kwargs.pop('parsers', None)
request = super(RequestFactory, self).options(*args, **kwargs)
return Request(request, parsers)

View File

@ -1,61 +0,0 @@
from django.contrib.auth.views import *
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext
import base64
# BLERGH
# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
# to add ADMIN_MEDIA_PREFIX to the RequestContext. I don't like this but really really want users to not have to
# be making settings changes in order to accomodate django-rest-framework
@csrf_protect
@never_cache
def api_login(request, template_name='djangorestframework/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm):
"""Displays the login form and handles the login action."""
redirect_to = request.REQUEST.get(redirect_field_name, '')
if request.method == "POST":
form = authentication_form(data=request.POST)
if form.is_valid():
# Light security check -- make sure redirect_to isn't garbage.
if not redirect_to or ' ' in redirect_to:
redirect_to = settings.LOGIN_REDIRECT_URL
# Heavier security check -- redirects to http://example.com should
# not be allowed, but things like /view/?param=http://example.com
# should be allowed. This regex checks if there is a '//' *before* a
# question mark.
elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
redirect_to = settings.LOGIN_REDIRECT_URL
# Okay, security checks complete. Log the user in.
auth_login(request, form.get_user())
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect(redirect_to)
else:
form = authentication_form(request)
request.session.set_test_cookie()
#current_site = get_current_site(request)
return render_to_response(template_name, {
'form': form,
redirect_field_name: redirect_to,
#'site': current_site,
#'site_name': current_site.name,
'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
}, context_instance=RequestContext(request))
def api_logout(request, next_page=None, template_name='djangorestframework/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
return logout(request, next_page, template_name, redirect_field_name)

View File

@ -6,15 +6,13 @@ By setting or modifying class attributes on your view, you change it's predefine
""" """
import re import re
from django.core.urlresolvers import set_script_prefix, get_script_prefix
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View as DjangoView, apply_markdown from djangorestframework.compat import View as DjangoView, apply_markdown
from djangorestframework.response import ImmediateResponse from djangorestframework.response import Response, ImmediateResponse
from djangorestframework.mixins import * from djangorestframework.mixins import *
from djangorestframework.utils import allowed_methods
from djangorestframework import resources, renderers, parsers, authentication, permissions, status from djangorestframework import resources, renderers, parsers, authentication, permissions, status
@ -81,12 +79,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
or `None` to use default behaviour. or `None` to use default behaviour.
""" """
renderer_classes = renderers.DEFAULT_RENDERERS renderers = renderers.DEFAULT_RENDERERS
""" """
List of renderer classes the resource can serialize the response with, ordered by preference. List of renderer classes the resource can serialize the response with, ordered by preference.
""" """
parser_classes = parsers.DEFAULT_PARSERS parsers = parsers.DEFAULT_PARSERS
""" """
List of parser classes the resource can parse the request with. List of parser classes the resource can parse the request with.
""" """
@ -118,7 +116,15 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
""" """
Return the list of allowed HTTP methods, uppercased. Return the list of allowed HTTP methods, uppercased.
""" """
return allowed_methods(self) return [method.upper() for method in self.http_method_names
if hasattr(self, method)]
@property
def default_response_headers(self):
return {
'Allow': ', '.join(self.allowed_methods),
'Vary': 'Authenticate, Accept'
}
def get_name(self): def get_name(self):
""" """
@ -183,32 +189,35 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
def initial(self, request, *args, **kargs): def initial(self, request, *args, **kargs):
""" """
Returns an `HttpRequest`. This method is a hook for any code that needs to run This method is a hook for any code that needs to run prior to
prior to anything else. anything else.
Required if you want to do things like set `request.upload_handlers` before Required if you want to do things like set `request.upload_handlers`
the authentication and dispatch handling is run. before the authentication and dispatch handling is run.
""" """
pass pass
def final(self, request, response, *args, **kargs): def final(self, request, response, *args, **kargs):
""" """
Returns an `HttpResponse`. This method is a hook for any code that needs to run This method is a hook for any code that needs to run after everything
after everything else in the view. else in the view.
Returns the final response object.
""" """
# Always add these headers. response.view = self
response['Allow'] = ', '.join(allowed_methods(self)) response.request = request
# sample to allow caching using Vary http header response.renderers = self.renderers
response['Vary'] = 'Authenticate, Accept' for key, value in self.headers.items():
response[key] = value
return response return response
# Note: session based authentication is explicitly CSRF validated, # Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt. # all other authentication is CSRF exempt.
@csrf_exempt @csrf_exempt
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.request = self.create_request(request) request = self.create_request(request)
self.request = request
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
self.headers = self.default_response_headers
try: try:
self.initial(request, *args, **kwargs) self.initial(request, *args, **kwargs)
@ -222,26 +231,17 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
else: else:
handler = self.http_method_not_allowed handler = self.http_method_not_allowed
# TODO: should we enforce HttpResponse, like Django does ?
response = handler(request, *args, **kwargs) response = handler(request, *args, **kwargs)
# Prepare response for the response cycle. if isinstance(response, Response):
self.response = response = self.prepare_response(response) # Pre-serialize filtering (eg filter complex objects into natively serializable types)
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
# TODO: ugly hack to handle both HttpResponse and Response.
if hasattr(response, 'raw_content'):
response.raw_content = self.filter_response(response.raw_content) response.raw_content = self.filter_response(response.raw_content)
else:
response.content = self.filter_response(response.content)
except ImmediateResponse, response: except ImmediateResponse, exc:
# Prepare response for the response cycle. response = exc.response
self.response = response = self.prepare_response(response)
# `final` is the last opportunity to temper with the response, or even self.response = self.final(request, response, *args, **kwargs)
# completely replace it. return self.response
return self.final(request, response, *args, **kwargs)
def options(self, request, *args, **kwargs): def options(self, request, *args, **kwargs):
content = { content = {
@ -266,7 +266,7 @@ class ModelView(View):
resource = resources.ModelResource resource = resources.ModelResource
class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView): class InstanceModelView(ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
""" """
A view which provides default operations for read/update/delete against a model instance. A view which provides default operations for read/update/delete against a model instance.
""" """

View File

@ -49,20 +49,20 @@ YAML
YAML support is optional, and requires `PyYAML`_. YAML support is optional, and requires `PyYAML`_.
Login / Logout Login / Logout
-------------- --------------
Django REST framework includes login and logout views that are useful if Django REST framework includes login and logout views that are needed if
you're using the self-documenting API:: you're using the self-documenting API.
from django.conf.urls.defaults import patterns Make sure you include the following in your `urlconf`::
urlpatterns = patterns('djangorestframework.views', from django.conf.urls.defaults import patterns, url
# Add your resources here
(r'^accounts/login/$', 'api_login'), urlpatterns = patterns('',
(r'^accounts/logout/$', 'api_logout'), ...
) url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
)
.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ .. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/ .. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/

View File

@ -64,6 +64,12 @@ To add Django REST framework to a Django project:
* Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``. * Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``.
* Add ``djangorestframework`` to your ``INSTALLED_APPS``. * Add ``djangorestframework`` to your ``INSTALLED_APPS``.
* Add the following to your URLconf. (To include the REST framework Login/Logout views.)::
urlpatterns = patterns('',
...
url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
)
For more information on settings take a look at the :ref:`setup` section. For more information on settings take a look at the :ref:`setup` section.

View File

@ -2,6 +2,7 @@ from django.db import models
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
import uuid import uuid
def uuid_str(): def uuid_str():
return str(uuid.uuid1()) return str(uuid.uuid1())
@ -14,6 +15,7 @@ RATING_CHOICES = ((0, 'Awful'),
MAX_POSTS = 10 MAX_POSTS = 10
class BlogPost(models.Model): class BlogPost(models.Model):
key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False) key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False)
title = models.CharField(max_length=128) title = models.CharField(max_length=128)
@ -37,4 +39,3 @@ class Comment(models.Model):
comment = models.TextField() comment = models.TextField()
rating = models.IntegerField(blank=True, null=True, choices=RATING_CHOICES, help_text='How did you rate this post?') rating = models.IntegerField(blank=True, null=True, choices=RATING_CHOICES, help_text='How did you rate this post?')
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)

View File

@ -11,8 +11,15 @@ class BlogPostResource(ModelResource):
fields = ('created', 'title', 'slug', 'content', 'url', 'comments') fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
ordering = ('-created',) ordering = ('-created',)
def url(self, instance):
return reverse('blog-post',
kwargs={'key': instance.key},
request=self.request)
def comments(self, instance): def comments(self, instance):
return reverse('comments', request, kwargs={'blogpost': instance.key}) return reverse('comments',
kwargs={'blogpost': instance.key},
request=self.request)
class CommentResource(ModelResource): class CommentResource(ModelResource):
@ -24,4 +31,6 @@ class CommentResource(ModelResource):
ordering = ('-created',) ordering = ('-created',)
def blogpost(self, instance): def blogpost(self, instance):
return reverse('blog-post', request, kwargs={'key': instance.blogpost.key}) return reverse('blog-post',
kwargs={'key': instance.blogpost.key},
request=self.request)

View File

@ -10,11 +10,12 @@ from django.conf.urls.defaults import patterns, url
class ExampleView(ResponseMixin, View): class ExampleView(ResponseMixin, View):
"""An example view using Django 1.3's class based views. """An example view using Django 1.3's class based views.
Uses djangorestframework's RendererMixin to provide support for multiple output formats.""" Uses djangorestframework's RendererMixin to provide support for multiple output formats."""
renderer_classes = DEFAULT_RENDERERS renderers = DEFAULT_RENDERERS
def get(self, request): def get(self, request):
url = reverse('mixin-view', request)
response = Response({'description': 'Some example content', response = Response({'description': 'Some example content',
'url': reverse('mixin-view', request)}, status=200) 'url': url}, status=200)
self.response = self.prepare_response(response) self.response = self.prepare_response(response)
return self.response return self.response
@ -22,4 +23,3 @@ class ExampleView(ResponseMixin, View):
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', ExampleView.as_view(), name='mixin-view'), url(r'^$', ExampleView.as_view(), name='mixin-view'),
) )

View File

@ -2,6 +2,7 @@ from django.db import models
MAX_INSTANCES = 10 MAX_INSTANCES = 10
class MyModel(models.Model): class MyModel(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.')
@ -15,5 +16,3 @@ class MyModel(models.Model):
super(MyModel, self).save(*args, **kwargs) super(MyModel, self).save(*args, **kwargs)
while MyModel.objects.all().count() > MAX_INSTANCES: while MyModel.objects.all().count() > MAX_INSTANCES:
MyModel.objects.all().order_by('-created')[0].delete() MyModel.objects.all().order_by('-created')[0].delete()

View File

@ -1,7 +1,14 @@
from djangorestframework.resources import ModelResource from djangorestframework.resources import ModelResource
from djangorestframework.reverse import reverse
from modelresourceexample.models import MyModel from modelresourceexample.models import MyModel
class MyModelResource(ModelResource): class MyModelResource(ModelResource):
model = MyModel model = MyModel
fields = ('foo', 'bar', 'baz', 'url') fields = ('foo', 'bar', 'baz', 'url')
ordering = ('created',) ordering = ('created',)
def url(self, instance):
return reverse('model-resource-instance',
kwargs={'id': instance.id},
request=self.request)

View File

@ -2,7 +2,10 @@ from django.conf.urls.defaults import patterns, url
from djangorestframework.views import ListOrCreateModelView, InstanceModelView from djangorestframework.views import ListOrCreateModelView, InstanceModelView
from modelresourceexample.resources import MyModelResource from modelresourceexample.resources import MyModelResource
my_model_list = ListOrCreateModelView.as_view(resource=MyModelResource)
my_model_instance = InstanceModelView.as_view(resource=MyModelResource)
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'), url(r'^$', my_model_list, name='model-resource-root'),
url(r'^(?P<pk>[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)), url(r'^(?P<id>[0-9]+)/$', my_model_instance, name='model-resource-instance'),
) )

View File

@ -28,6 +28,20 @@ def remove_oldest_files(dir, max_files):
[os.remove(path) for path in ctime_sorted_paths[max_files:]] [os.remove(path) for path in ctime_sorted_paths[max_files:]]
def get_filename(key):
"""
Given a stored object's key returns the file's path.
"""
return os.path.join(OBJECT_STORE_DIR, key)
def get_file_url(key, request):
"""
Given a stored object's key returns the URL for the object.
"""
return reverse('stored-object', kwargs={'key': key}, request=request)
class ObjectStoreRoot(View): class ObjectStoreRoot(View):
""" """
Root of the Object Store API. Root of the Object Store API.
@ -38,20 +52,25 @@ class ObjectStoreRoot(View):
""" """
Return a list of all the stored object URLs. (Ordered by creation time, newest first) Return a list of all the stored object URLs. (Ordered by creation time, newest first)
""" """
filepaths = [os.path.join(OBJECT_STORE_DIR, file) for file in os.listdir(OBJECT_STORE_DIR) if not file.startswith('.')] filepaths = [os.path.join(OBJECT_STORE_DIR, file)
for file in os.listdir(OBJECT_STORE_DIR)
if not file.startswith('.')]
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 Response([reverse('stored-object', request, kwargs={'key':key}) for key in ctime_sorted_basenames]) content = [get_file_url(key, request)
for key in ctime_sorted_basenames]
return Response(content)
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.
""" """
key = str(uuid.uuid1()) key = str(uuid.uuid1())
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
pickle.dump(self.CONTENT, open(pathname, 'wb')) pickle.dump(self.CONTENT, open(filename, 'wb'))
remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES) remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES)
url = reverse('stored-object', request, kwargs={'key':key}) url = get_file_url(key, request)
return Response(self.CONTENT, status.HTTP_201_CREATED, {'Location': url}) return Response(self.CONTENT, status.HTTP_201_CREATED, {'Location': url})
@ -60,30 +79,31 @@ class StoredObject(View):
Represents a stored object. Represents a stored object.
The object may be any picklable content. The object may be any picklable content.
""" """
def get(self, request, key): def get(self, request, key):
""" """
Return a stored object, by unpickling the contents of a locally stored file. Return a stored object, by unpickling the contents of a locally
stored file.
""" """
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
if not os.path.exists(pathname): if not os.path.exists(filename):
return Response(status=status.HTTP_404_NOT_FOUND) return Response(status=status.HTTP_404_NOT_FOUND)
return Response(pickle.load(open(pathname, 'rb'))) return Response(pickle.load(open(filename, 'rb')))
def put(self, request, key): def put(self, request, key):
""" """
Update/create a stored object, by pickling the request content to a locally stored file. Update/create a stored object, by pickling the request content to a
locally stored file.
""" """
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
pickle.dump(self.CONTENT, open(pathname, 'wb')) pickle.dump(self.CONTENT, open(filename, 'wb'))
return Response(self.CONTENT) return Response(self.CONTENT)
def delete(self, request, key): def delete(self, request, key):
""" """
Delete a stored object, by removing it's pickled file. Delete a stored object, by removing it's pickled file.
""" """
pathname = os.path.join(OBJECT_STORE_DIR, key) filename = get_filename(key)
if not os.path.exists(pathname): if not os.path.exists(filename):
return Response(status=status.HTTP_404_NOT_FOUND) return Response(status=status.HTTP_404_NOT_FOUND)
os.remove(pathname) os.remove(filename)
return Response() return Response()

View File

@ -6,6 +6,7 @@ from pygments.styles import get_all_styles
LEXER_CHOICES = sorted([(item[1][0], item[0]) for item in get_all_lexers()]) LEXER_CHOICES = sorted([(item[1][0], item[0]) for item in get_all_lexers()])
STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles())) STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles()))
class PygmentsForm(forms.Form): class PygmentsForm(forms.Form):
"""A simple form with some of the most important pygments settings. """A simple form with some of the most important pygments settings.
The code to be highlighted can be specified either in a text field, or by URL. The code to be highlighted can be specified either in a text field, or by URL.
@ -24,5 +25,3 @@ class PygmentsForm(forms.Form):
initial='python') initial='python')
style = forms.ChoiceField(choices=STYLE_CHOICES, style = forms.ChoiceField(choices=STYLE_CHOICES,
initial='friendly') initial='friendly')

View File

@ -14,13 +14,13 @@ class TestPygmentsExample(TestCase):
self.factory = RequestFactory() self.factory = RequestFactory()
self.temp_dir = tempfile.mkdtemp() self.temp_dir = tempfile.mkdtemp()
views.HIGHLIGHTED_CODE_DIR = self.temp_dir views.HIGHLIGHTED_CODE_DIR = self.temp_dir
def tearDown(self): def tearDown(self):
try: try:
shutil.rmtree(self.temp_dir) shutil.rmtree(self.temp_dir)
except Exception: except Exception:
pass pass
def test_get_to_root(self): def test_get_to_root(self):
'''Just do a get on the base url''' '''Just do a get on the base url'''
request = self.factory.get('/pygments') request = self.factory.get('/pygments')
@ -44,6 +44,3 @@ class TestPygmentsExample(TestCase):
response = view(request) response = view(request)
response_locations = json.loads(response.content) response_locations = json.loads(response.content)
self.assertEquals(locations, response_locations) self.assertEquals(locations, response_locations)

View File

@ -1,7 +1,6 @@
from __future__ import with_statement # for python 2.5 from __future__ import with_statement # for python 2.5
from django.conf import settings from django.conf import settings
from djangorestframework.resources import FormResource
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.renderers import BaseRenderer from djangorestframework.renderers import BaseRenderer
from djangorestframework.reverse import reverse from djangorestframework.reverse import reverse
@ -30,9 +29,13 @@ def list_dir_sorted_by_ctime(dir):
""" """
Return a list of files sorted by creation time Return a list of files sorted by creation time
""" """
filepaths = [os.path.join(dir, file) for file in os.listdir(dir) if not file.startswith('.')] filepaths = [os.path.join(dir, file)
return [item[0] for item in sorted( [(path, os.path.getctime(path)) for path in filepaths], for file in os.listdir(dir)
key=operator.itemgetter(1), reverse=False) ] if not file.startswith('.')]
ctimes = [(path, os.path.getctime(path)) for path in filepaths]
ctimes = sorted(ctimes, key=operator.itemgetter(1), reverse=False)
return [filepath for filepath, ctime in ctimes]
def remove_oldest_files(dir, max_files): def remove_oldest_files(dir, max_files):
""" """
@ -60,8 +63,11 @@ class PygmentsRoot(View):
""" """
Return a list of all currently existing snippets. Return a list of all currently existing snippets.
""" """
unique_ids = [os.path.split(f)[1] for f in list_dir_sorted_by_ctime(HIGHLIGHTED_CODE_DIR)] unique_ids = [os.path.split(f)[1]
return Response([reverse('pygments-instance', request, args=[unique_id]) for unique_id in unique_ids]) for f in list_dir_sorted_by_ctime(HIGHLIGHTED_CODE_DIR)]
urls = [reverse('pygments-instance', args=[unique_id], request=request)
for unique_id in unique_ids]
return Response(urls)
def post(self, request): def post(self, request):
""" """
@ -81,7 +87,7 @@ class PygmentsRoot(View):
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES) remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
location = reverse('pygments-instance', request, args=[unique_id]) location = reverse('pygments-instance', args=[unique_id], request=request)
return Response(status=status.HTTP_201_CREATED, headers={'Location': location}) return Response(status=status.HTTP_201_CREATED, headers={'Location': location})
@ -90,7 +96,7 @@ class PygmentsInstance(View):
Simply return the stored highlighted HTML file with the correct mime type. Simply return the stored highlighted HTML file with the correct mime type.
This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class. This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class.
""" """
renderer_classes = (HTMLRenderer,) renderers = (HTMLRenderer, )
def get(self, request, unique_id): def get(self, request, unique_id):
""" """
@ -110,4 +116,3 @@ class PygmentsInstance(View):
return Response(status=status.HTTP_404_NOT_FOUND) return Response(status=status.HTTP_404_NOT_FOUND)
os.remove(pathname) os.remove(pathname)
return Response() return Response()

View File

@ -22,7 +22,7 @@ class MyBaseViewUsingEnhancedRequest(RequestMixin, View):
Base view enabling the usage of enhanced requests with user defined views. Base view enabling the usage of enhanced requests with user defined views.
""" """
parser_classes = parsers.DEFAULT_PARSERS parsers = parsers.DEFAULT_PARSERS
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.request = request = self.create_request(request) self.request = request = self.create_request(request)
@ -41,4 +41,3 @@ class EchoRequestContentView(MyBaseViewUsingEnhancedRequest):
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
return HttpResponse(("Found %s in request.DATA, content : %s" % return HttpResponse(("Found %s in request.DATA, content : %s" %
(type(request.DATA), request.DATA))) (type(request.DATA), request.DATA)))

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
class MyForm(forms.Form): class MyForm(forms.Form):
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.')

View File

@ -16,9 +16,11 @@ class ExampleView(View):
Handle GET requests, returning a list of URLs pointing to Handle GET requests, returning a list of URLs pointing to
three other views. three other views.
""" """
urls = [reverse('another-example', request, kwargs={'num': num}) resource_urls = [reverse('another-example',
for num in range(3)] kwargs={'num': num},
return Response({"Some other resources": urls}) request=request)
for num in range(3)]
return Response({"Some other resources": resource_urls})
class AnotherExampleView(View): class AnotherExampleView(View):

View File

@ -19,7 +19,7 @@ class Sandbox(View):
For example, to get the default representation using curl: For example, to get the default representation using curl:
bash: curl -X GET http://rest.ep.io/ bash: curl -X GET http://rest.ep.io/
Or, to get the plaintext documentation represention: Or, to get the plaintext documentation represention:
bash: curl -X GET http://rest.ep.io/ -H 'Accept: text/plain' bash: curl -X GET http://rest.ep.io/ -H 'Accept: text/plain'
@ -49,19 +49,19 @@ class Sandbox(View):
def get(self, request): def get(self, request):
return Response([ return Response([
{'name': 'Simple Resource example', {'name': 'Simple Resource example',
'url': reverse('example-resource', request)}, 'url': reverse('example-resource', request=request)},
{'name': 'Simple ModelResource example', {'name': 'Simple ModelResource example',
'url': reverse('model-resource-root', request)}, 'url': reverse('model-resource-root', request=request)},
{'name': 'Simple Mixin-only example', {'name': 'Simple Mixin-only example',
'url': reverse('mixin-view', request)}, 'url': reverse('mixin-view', request=request)},
{'name': 'Object store API' {'name': 'Object store API',
'url': reverse('object-store-root', request)}, 'url': reverse('object-store-root', request=request)},
{'name': 'Code highlighting API', {'name': 'Code highlighting API',
'url': reverse('pygments-root', request)}, 'url': reverse('pygments-root', request=request)},
{'name': 'Blog posts API', {'name': 'Blog posts API',
'url': reverse('blog-posts-root', request)}, 'url': reverse('blog-posts-root', request=request)},
{'name': 'Permissions example', {'name': 'Permissions example',
'url': reverse('permissions-example', request)}, 'url': reverse('permissions-example', request=request)},
{'name': 'Simple request mixin example', {'name': 'Simple request mixin example',
'url': reverse('request-example', request)} 'url': reverse('request-example', request=request)}
]) ])

View File

@ -1,4 +1,4 @@
from django.conf.urls.defaults import patterns, include from django.conf.urls.defaults import patterns, include, url
from sandbox.views import Sandbox from sandbox.views import Sandbox
try: try:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib.staticfiles.urls import staticfiles_urlpatterns
@ -15,9 +15,7 @@ urlpatterns = patterns('',
(r'^pygments/', include('pygments_api.urls')), (r'^pygments/', include('pygments_api.urls')),
(r'^blog-post/', include('blogpost.urls')), (r'^blog-post/', include('blogpost.urls')),
(r'^permissions-example/', include('permissionsexample.urls')), (r'^permissions-example/', include('permissionsexample.urls')),
(r'^request-example/', include('requestexample.urls')), url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
(r'^', include('djangorestframework.urls')),
) )
urlpatterns += staticfiles_urlpatterns() urlpatterns += staticfiles_urlpatterns()