mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-03-22 02:44:27 +03:00
Massive merge
This commit is contained in:
parent
5fd4c639d7
commit
1cde31c86d
1
AUTHORS
1
AUTHORS
|
@ -33,6 +33,7 @@ Camille Harang <mammique>
|
|||
Paul Oswald <poswald>
|
||||
Sean C. Farley <scfarley>
|
||||
Daniel Izquierdo <izquierdo>
|
||||
Can Yavuz <tschan>
|
||||
|
||||
THANKS TO:
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
__version__ = '0.3.3'
|
||||
__version__ = '0.4.0-dev'
|
||||
|
||||
VERSION = __version__ # synonym
|
||||
|
|
|
@ -87,8 +87,6 @@ class UserLoggedInAuthentication(BaseAuthentication):
|
|||
Returns a :obj:`User` if the request session currently has a logged in user.
|
||||
Otherwise returns :const:`None`.
|
||||
"""
|
||||
request.DATA # Make sure our generic parsing runs first
|
||||
|
||||
if getattr(request, 'user', None) and request.user.is_active:
|
||||
# Enforce CSRF validation for session based authentication.
|
||||
resp = CsrfViewMiddleware().process_view(request, None, (), {})
|
||||
|
|
|
@ -214,18 +214,15 @@ else:
|
|||
REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
|
||||
REASON_BAD_TOKEN = "CSRF token missing or incorrect."
|
||||
|
||||
|
||||
def _get_failure_view():
|
||||
"""
|
||||
Returns the view to be used for CSRF rejections
|
||||
"""
|
||||
return get_callable(settings.CSRF_FAILURE_VIEW)
|
||||
|
||||
|
||||
def _get_new_csrf_key():
|
||||
return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
|
||||
|
||||
|
||||
def get_token(request):
|
||||
"""
|
||||
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
|
||||
return request.META.get("CSRF_COOKIE", None)
|
||||
|
||||
|
||||
def _sanitize_token(token):
|
||||
# Allow only alphanum, and ensure we return a 'str' for the sake of the post
|
||||
# processing middleware.
|
||||
|
@ -432,12 +428,13 @@ try:
|
|||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
|
||||
import unittest
|
||||
try:
|
||||
import unittest.skip
|
||||
except ImportError: # python < 2.7
|
||||
except ImportError: # python < 2.7
|
||||
from unittest import TestCase
|
||||
import functools
|
||||
import functools
|
||||
|
||||
def skip(reason):
|
||||
# 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)):
|
||||
@functools.wraps(test_item)
|
||||
def skip_wrapper(*args, **kwargs):
|
||||
pass
|
||||
pass
|
||||
test_item = skip_wrapper
|
||||
|
||||
test_item.__unittest_skip__ = True
|
||||
test_item.__unittest_skip_why__ = reason
|
||||
return test_item
|
||||
return decorator
|
||||
|
||||
|
||||
unittest.skip = skip
|
||||
|
||||
# reverse_lazy (Django 1.4 onwards)
|
||||
|
||||
# xml.etree.parse only throws ParseError for python >= 2.7
|
||||
try:
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
except:
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.functional import lazy
|
||||
reverse_lazy = lazy(reverse, str)
|
||||
from xml.etree import ParseError as ETParseError
|
||||
except ImportError: # python < 2.7
|
||||
ETParseError = None
|
||||
|
|
|
@ -21,14 +21,13 @@ __all__ = (
|
|||
'ResponseMixin',
|
||||
'AuthMixin',
|
||||
'ResourceMixin',
|
||||
# Reverse URL lookup behavior
|
||||
'InstanceMixin',
|
||||
# Model behavior mixins
|
||||
'ReadModelMixin',
|
||||
'CreateModelMixin',
|
||||
'UpdateModelMixin',
|
||||
'DeleteModelMixin',
|
||||
'ListModelMixin'
|
||||
'ListModelMixin',
|
||||
'PaginatorMixin'
|
||||
)
|
||||
|
||||
|
||||
|
@ -39,39 +38,33 @@ class RequestMixin(object):
|
|||
`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
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Creates and returns an instance of :class:`request.Request`.
|
||||
This new instance wraps the `request` passed as a parameter, and use the
|
||||
parsers set on the view.
|
||||
This new instance wraps the `request` passed as a parameter, and use
|
||||
the parsers set on the view.
|
||||
"""
|
||||
parsers = self.get_parsers()
|
||||
return self.request_class(request, parsers=parsers)
|
||||
return self.request_class(request, parsers=self.parsers)
|
||||
|
||||
@property
|
||||
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 ##########
|
||||
|
||||
|
@ -80,58 +73,32 @@ class ResponseMixin(object):
|
|||
`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.
|
||||
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
|
||||
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
|
||||
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 ##########
|
||||
|
@ -254,30 +221,6 @@ class ResourceMixin(object):
|
|||
else:
|
||||
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 ##########
|
||||
|
||||
|
@ -411,7 +354,7 @@ class CreateModelMixin(ModelMixin):
|
|||
response = Response(instance, status=status.HTTP_201_CREATED)
|
||||
|
||||
# Set headers
|
||||
if hasattr(instance, 'get_absolute_url'):
|
||||
if hasattr(self.resource, 'url'):
|
||||
response['Location'] = self.resource(self).url(instance)
|
||||
return response
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ from djangorestframework.compat import yaml
|
|||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework.utils.mediatypes import media_type_matches
|
||||
from xml.etree import ElementTree as ET
|
||||
from djangorestframework.compat import ETParseError
|
||||
from xml.parsers.expat import ExpatError
|
||||
import datetime
|
||||
import decimal
|
||||
|
||||
|
@ -43,13 +45,6 @@ class BaseParser(object):
|
|||
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
|
||||
def parse(self, stream):
|
||||
def parse(self, stream, meta, upload_handlers):
|
||||
"""
|
||||
Given a *stream* to read from, return the deserialized output.
|
||||
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):
|
||||
|
@ -78,7 +73,7 @@ class JSONParser(BaseParser):
|
|||
|
||||
media_type = 'application/json'
|
||||
|
||||
def parse(self, stream):
|
||||
def parse(self, stream, meta, upload_handlers):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
|
@ -93,29 +88,26 @@ class JSONParser(BaseParser):
|
|||
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`.
|
||||
"""
|
||||
|
||||
media_type = 'application/yaml'
|
||||
|
||||
def parse(self, stream):
|
||||
"""
|
||||
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:
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
class PlainTextParser(BaseParser):
|
||||
|
@ -125,7 +117,7 @@ class PlainTextParser(BaseParser):
|
|||
|
||||
media_type = 'text/plain'
|
||||
|
||||
def parse(self, stream):
|
||||
def parse(self, stream, meta, upload_handlers):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
|
@ -142,7 +134,7 @@ class FormParser(BaseParser):
|
|||
|
||||
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)`.
|
||||
|
||||
|
@ -160,21 +152,20 @@ class MultiPartParser(BaseParser):
|
|||
|
||||
media_type = 'multipart/form-data'
|
||||
|
||||
def parse(self, stream):
|
||||
def parse(self, stream, meta, upload_handlers):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
`data` will be a :class:`QueryDict` containing all the form parameters.
|
||||
`files` will be a :class:`QueryDict` containing all the form files.
|
||||
"""
|
||||
upload_handlers = self.view.request._get_upload_handlers()
|
||||
try:
|
||||
django_parser = DjangoMultiPartParser(self.view.request.META, stream, upload_handlers)
|
||||
parser = DjangoMultiPartParser(meta, stream, upload_handlers)
|
||||
return parser.parse()
|
||||
except MultiPartParserError, exc:
|
||||
raise ImmediateResponse(
|
||||
{'detail': 'multipart parse error - %s' % unicode(exc)},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
return django_parser.parse()
|
||||
|
||||
|
||||
class XMLParser(BaseParser):
|
||||
|
@ -184,14 +175,18 @@ class XMLParser(BaseParser):
|
|||
|
||||
media_type = 'application/xml'
|
||||
|
||||
def parse(self, stream):
|
||||
def parse(self, stream, meta, upload_handlers):
|
||||
"""
|
||||
Returns a 2-tuple of `(data, files)`.
|
||||
|
||||
`data` will simply be a string representing the body of the request.
|
||||
`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())
|
||||
|
||||
return (data, None)
|
||||
|
@ -251,5 +246,7 @@ DEFAULT_PARSERS = (
|
|||
XMLParser
|
||||
)
|
||||
|
||||
if YAMLParser:
|
||||
DEFAULT_PARSERS += (YAMLParser,)
|
||||
if yaml:
|
||||
DEFAULT_PARSERS += (YAMLParser, )
|
||||
else:
|
||||
YAMLParser = None
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DateTimeAwareJSONEncoder
|
||||
from django.template import RequestContext, loader
|
||||
from django.utils import simplejson as json
|
||||
|
||||
|
||||
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.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
|
||||
from djangorestframework import VERSION
|
||||
|
||||
import string
|
||||
from urllib import quote_plus
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseRenderer',
|
||||
|
@ -156,25 +154,22 @@ class XMLRenderer(BaseRenderer):
|
|||
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'
|
||||
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
|
||||
return yaml.safe_dump(obj)
|
||||
|
||||
|
||||
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.)
|
||||
renderers = [renderer for renderer in view.renderer_classes
|
||||
if not issubclass(renderer, DocumentingTemplateRenderer)]
|
||||
renderers = [renderer for renderer in view.renderers
|
||||
if not issubclass(renderer, DocumentingTemplateRenderer)]
|
||||
if not renderers:
|
||||
return '[No renderers were found]'
|
||||
|
||||
|
@ -278,14 +273,14 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
|
||||
# NB. http://jacobian.org/writing/dynamic-form-generation/
|
||||
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,
|
||||
as they are determined by the Resource the form is being created against.
|
||||
Add the fields dynamically."""
|
||||
super(GenericContentForm, self).__init__()
|
||||
|
||||
contenttype_choices = [(media_type, media_type) for media_type in request._parsed_media_types]
|
||||
initial_contenttype = request._default_parser.media_type
|
||||
contenttype_choices = [(media_type, media_type) for media_type in view._parsed_media_types]
|
||||
initial_contenttype = view._default_parser.media_type
|
||||
|
||||
self.fields[request._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type',
|
||||
choices=contenttype_choices,
|
||||
|
@ -298,7 +293,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
return None
|
||||
|
||||
# Okey doke, let's do it
|
||||
return GenericContentForm(view.request)
|
||||
return GenericContentForm(view, view.request)
|
||||
|
||||
def get_name(self):
|
||||
try:
|
||||
|
@ -327,13 +322,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
put_form_instance = self._get_form_instance(self.view, 'put')
|
||||
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()
|
||||
description = self.get_description()
|
||||
|
||||
|
@ -343,21 +331,18 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
context = RequestContext(self.view.request, {
|
||||
'content': content,
|
||||
'view': self.view,
|
||||
'request': self.view.request, # TODO: remove
|
||||
'request': self.view.request,
|
||||
'response': self.view.response,
|
||||
'description': description,
|
||||
'name': name,
|
||||
'version': VERSION,
|
||||
'breadcrumblist': breadcrumb_list,
|
||||
'allowed_methods': allowed_methods(self.view),
|
||||
'allowed_methods': self.view.allowed_methods,
|
||||
'available_formats': self.view._rendered_formats,
|
||||
'put_form': put_form_instance,
|
||||
'post_form': post_form_instance,
|
||||
'login_url': login_url,
|
||||
'logout_url': logout_url,
|
||||
'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
|
||||
'METHOD_PARAM': getattr(self.view.request, '_METHOD_PARAM', None),
|
||||
'ADMIN_MEDIA_PREFIX': getattr(settings, 'ADMIN_MEDIA_PREFIX', None),
|
||||
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
|
||||
})
|
||||
|
||||
ret = template.render(context)
|
||||
|
@ -415,5 +400,7 @@ DEFAULT_RENDERERS = (
|
|||
XMLRenderer
|
||||
)
|
||||
|
||||
if YAMLRenderer:
|
||||
DEFAULT_RENDERERS += (YAMLRenderer,)
|
||||
if yaml:
|
||||
DEFAULT_RENDERERS += (YAMLRenderer, )
|
||||
else:
|
||||
YAMLRenderer = None
|
||||
|
|
|
@ -4,15 +4,14 @@ object received in all the views.
|
|||
|
||||
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
|
||||
- form overloading of HTTP method, content type and content
|
||||
"""
|
||||
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework import status
|
||||
from djangorestframework.utils.mediatypes import is_form_media_type
|
||||
from djangorestframework.utils import as_tuple
|
||||
|
||||
from StringIO import StringIO
|
||||
|
||||
|
@ -20,6 +19,14 @@ from StringIO import StringIO
|
|||
__all__ = ('Request',)
|
||||
|
||||
|
||||
class Empty:
|
||||
pass
|
||||
|
||||
|
||||
def _hasattr(obj, name):
|
||||
return not getattr(obj, name) is Empty
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""
|
||||
Wrapper allowing to enhance a standard `HttpRequest` instance.
|
||||
|
@ -35,19 +42,29 @@ class Request(object):
|
|||
_CONTENT_PARAM = '_content'
|
||||
|
||||
def __init__(self, request=None, parsers=None):
|
||||
self.request = request
|
||||
if parsers is not None:
|
||||
self.parsers = parsers
|
||||
self._request = request
|
||||
self.parsers = parsers or ()
|
||||
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
|
||||
def method(self):
|
||||
"""
|
||||
Returns the HTTP method.
|
||||
|
||||
This allows the `method` to be overridden by using a hidden `form` field
|
||||
on a form POST request.
|
||||
This allows the `method` to be overridden by using a hidden `form`
|
||||
field on a form POST request.
|
||||
"""
|
||||
if not hasattr(self, '_method'):
|
||||
if not _hasattr(self, '_method'):
|
||||
self._load_method_and_content_type()
|
||||
return self._method
|
||||
|
||||
|
@ -60,10 +77,19 @@ class Request(object):
|
|||
as it allows the content type to be overridden by using a hidden form
|
||||
field on a form POST request.
|
||||
"""
|
||||
if not hasattr(self, '_content_type'):
|
||||
if not _hasattr(self, '_content_type'):
|
||||
self._load_method_and_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
|
||||
def DATA(self):
|
||||
"""
|
||||
|
@ -72,7 +98,7 @@ class Request(object):
|
|||
Similar to ``request.POST``, except that it handles arbitrary parsers,
|
||||
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()
|
||||
return self._data
|
||||
|
||||
|
@ -83,7 +109,7 @@ class Request(object):
|
|||
Similar to ``request.FILES``, except that it handles arbitrary parsers,
|
||||
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()
|
||||
return self._files
|
||||
|
||||
|
@ -91,11 +117,11 @@ class Request(object):
|
|||
"""
|
||||
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()
|
||||
|
||||
if not hasattr(self, '_data'):
|
||||
(self._data, self._files) = self._parse(self._get_stream(), self._content_type)
|
||||
if not _hasattr(self, '_data'):
|
||||
(self._data, self._files) = self._parse()
|
||||
|
||||
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._perform_form_overloading()
|
||||
# if the HTTP method was not overloaded, we take the raw HTTP method
|
||||
if not hasattr(self, '_method'):
|
||||
self._method = self.request.method
|
||||
|
||||
def _get_stream(self):
|
||||
"""
|
||||
Returns an object that may be used to stream the request content.
|
||||
"""
|
||||
if not _hasattr(self, '_method'):
|
||||
self._method = self._request.method
|
||||
|
||||
def _load_stream(self):
|
||||
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):
|
||||
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:
|
||||
return None
|
||||
elif hasattr(self, 'read'):
|
||||
return self
|
||||
return StringIO(self.raw_post_data)
|
||||
self._stream = None
|
||||
elif hasattr(self._request, 'read'):
|
||||
self._stream = self._request
|
||||
else:
|
||||
self._stream = StringIO(self.raw_post_data)
|
||||
|
||||
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
|
||||
overridden by setting them in hidden form fields or not.
|
||||
If this is a form POST request, then we need to check if the method and
|
||||
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.
|
||||
if (not self._USE_FORM_OVERLOADING or self.request.method != 'POST'
|
||||
or not is_form_media_type(self._content_type)):
|
||||
if (not self._USE_FORM_OVERLOADING
|
||||
or self._request.method != 'POST'
|
||||
or not is_form_media_type(self._content_type)):
|
||||
return
|
||||
|
||||
# At this point we're committed to parsing the request as form data.
|
||||
self._data = data = self.POST.copy()
|
||||
self._files = self.FILES
|
||||
self._data = self._request.POST
|
||||
self._files = self._request.FILES
|
||||
|
||||
# Method overloading - change the method and remove the param from the content.
|
||||
if self._METHOD_PARAM in data:
|
||||
# NOTE: unlike `get`, `pop` on a `QueryDict` seems to return a list of values.
|
||||
if self._METHOD_PARAM in self._data:
|
||||
# NOTE: `pop` on a `QueryDict` returns a list of values.
|
||||
self._method = self._data.pop(self._METHOD_PARAM)[0].upper()
|
||||
|
||||
# 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]
|
||||
stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
|
||||
(self._data, self._files) = self._parse(stream, self._content_type)
|
||||
self._stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
|
||||
(self._data, self._files) = self._parse()
|
||||
|
||||
def _parse(self, stream, content_type):
|
||||
def _parse(self):
|
||||
"""
|
||||
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)
|
||||
|
||||
for parser in as_tuple(self.parsers):
|
||||
if parser.can_handle_request(content_type):
|
||||
return parser.parse(stream)
|
||||
for parser in self.get_parsers():
|
||||
if parser.can_handle_request(self.content_type):
|
||||
return parser.parse(self.stream, self.META, self.upload_handlers)
|
||||
|
||||
raise ImmediateResponse({
|
||||
'error': 'Unsupported media type in request \'%s\'.' % content_type},
|
||||
status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
|
||||
self._raise_415_response(self._content_type)
|
||||
|
||||
@property
|
||||
def _parsed_media_types(self):
|
||||
def _raise_415_response(self, content_type):
|
||||
"""
|
||||
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
|
||||
def _default_parser(self):
|
||||
"""
|
||||
Return the view's default parser class.
|
||||
"""
|
||||
return self.parsers[0]
|
||||
|
||||
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)
|
||||
raise ImmediateResponse(
|
||||
{
|
||||
'error': 'Unsupported media type in request \'%s\'.'
|
||||
% content_type
|
||||
},
|
||||
status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
When an attribute is not present on the calling instance, try to get it
|
||||
from the original request.
|
||||
Proxy other attributes to the underlying HttpRequest object.
|
||||
"""
|
||||
if hasattr(self.request, name):
|
||||
return getattr(self.request, name)
|
||||
else:
|
||||
return super(Request, self).__getattribute__(name)
|
||||
return getattr(self._request, name)
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
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.reverse import reverse
|
||||
from djangorestframework.serializer import Serializer, _SkipField
|
||||
from djangorestframework.utils import as_tuple, reverse
|
||||
from djangorestframework.serializer import Serializer
|
||||
from djangorestframework.utils import as_tuple
|
||||
|
||||
|
||||
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
|
||||
include = None
|
||||
|
@ -19,11 +16,13 @@ class BaseResource(Serializer):
|
|||
def __init__(self, view=None, depth=None, stack=[], **kwargs):
|
||||
super(BaseResource, self).__init__(depth, stack, **kwargs)
|
||||
self.view = view
|
||||
self.request = getattr(view, 'request', None)
|
||||
|
||||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
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
|
||||
|
||||
|
@ -37,7 +36,8 @@ class BaseResource(Serializer):
|
|||
class Resource(BaseResource):
|
||||
"""
|
||||
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.
|
||||
|
@ -220,9 +220,6 @@ class ModelResource(FormResource):
|
|||
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
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
include = ('url',)
|
||||
include = ()
|
||||
"""
|
||||
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()
|
||||
|
||||
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
|
||||
def _model_fields_set(self):
|
||||
"""
|
||||
|
|
|
@ -27,6 +27,10 @@ from djangorestframework import status
|
|||
__all__ = ('Response', 'ImmediateResponse')
|
||||
|
||||
|
||||
class NotAcceptable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Response(SimpleTemplateResponse):
|
||||
"""
|
||||
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
|
||||
_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,
|
||||
# which we don't need
|
||||
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.has_content_body = content is not None
|
||||
self.request = request
|
||||
self.headers = headers and headers[:] or []
|
||||
if renderers is not None:
|
||||
self.renderers = renderers
|
||||
self.view = view
|
||||
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
|
||||
def rendered_content(self):
|
||||
"""
|
||||
The final rendered content. Accessing this attribute triggers the complete rendering cycle :
|
||||
selecting suitable renderer, setting response's actual content type, rendering data.
|
||||
The final rendered content. Accessing this attribute triggers the
|
||||
complete rendering cycle: selecting suitable renderer, setting
|
||||
response's actual content type, rendering data.
|
||||
"""
|
||||
renderer, media_type = self._determine_renderer()
|
||||
|
||||
|
@ -70,6 +79,13 @@ class Response(SimpleTemplateResponse):
|
|||
return renderer.render(self.raw_content, media_type)
|
||||
return renderer.render()
|
||||
|
||||
def render(self):
|
||||
try:
|
||||
return super(Response, self).render()
|
||||
except NotAcceptable:
|
||||
response = self._get_406_response()
|
||||
return response.render()
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""
|
||||
|
@ -88,8 +104,6 @@ class Response(SimpleTemplateResponse):
|
|||
If those are useless, a default value is returned instead.
|
||||
"""
|
||||
request = self.request
|
||||
if request is None:
|
||||
return ['*/*']
|
||||
|
||||
if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
|
||||
# Use _accept parameter override
|
||||
|
@ -108,70 +122,52 @@ class Response(SimpleTemplateResponse):
|
|||
|
||||
def _determine_renderer(self):
|
||||
"""
|
||||
Determines the appropriate renderer for the output, given the list of accepted media types,
|
||||
and the :attr:`renderers` set on this class.
|
||||
Determines the appropriate renderer for the output, given the list of
|
||||
accepted media types, and the :attr:`renderers` set on this class.
|
||||
|
||||
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,
|
||||
# attempting more specific media types first
|
||||
# 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)
|
||||
for media_type_list in order_by_precedence(self._determine_accept_list()):
|
||||
for renderer in self.renderers:
|
||||
for media_type_list in order_by_precedence(accepts):
|
||||
for renderer in renderers:
|
||||
for media_type in media_type_list:
|
||||
if renderer.can_handle_response(media_type):
|
||||
return renderer, media_type
|
||||
|
||||
# No acceptable renderers were found
|
||||
raise ImmediateResponse({'detail': 'Could not satisfy the client\'s Accept header',
|
||||
'available_types': self._rendered_media_types},
|
||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
renderers=self.renderers)
|
||||
raise NotAcceptable
|
||||
|
||||
def _get_renderers(self):
|
||||
if hasattr(self, '_renderers'):
|
||||
return self._renderers
|
||||
return ()
|
||||
|
||||
def _set_renderers(self, value):
|
||||
self._renderers = value
|
||||
|
||||
renderers = property(_get_renderers, _set_renderers)
|
||||
|
||||
@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]
|
||||
def _get_406_response(self):
|
||||
renderer = self.renderers[0]
|
||||
return Response(
|
||||
{
|
||||
'detail': 'Could not satisfy the client\'s Accept header',
|
||||
'available_types': [renderer.media_type
|
||||
for renderer in self.renderers]
|
||||
},
|
||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
view=self.view, request=self.request, renderers=[renderer])
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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__)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.response = Response(*args, **kwargs)
|
||||
|
|
|
@ -2,22 +2,19 @@
|
|||
Provide reverse functions that return fully qualified URLs
|
||||
"""
|
||||
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
|
||||
*request* to build a fully qualified URL.
|
||||
Same as `django.core.urlresolvers.reverse`, but optionally takes a request
|
||||
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)
|
||||
return request.build_absolute_uri(url)
|
||||
if request:
|
||||
return request.build_absolute_uri(url)
|
||||
return url
|
||||
|
||||
|
||||
def reverse_lazy(viewname, request, *args, **kwargs):
|
||||
"""
|
||||
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)
|
||||
reverse_lazy = lazy(reverse, str)
|
||||
|
|
|
@ -53,11 +53,6 @@ MEDIA_ROOT = ''
|
|||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||
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.
|
||||
SECRET_KEY = 'u@x-aj9(hoh#rb-^ymf#g2jx_hp0vj7u5#b@ag1n^seu9e!%cy'
|
||||
|
||||
|
|
|
@ -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>
|
||||
</div>
|
||||
<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 %}{% endblock %}
|
||||
{% block userlinks %}
|
||||
{% 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>
|
||||
{% block nav-global %}{% endblock %}
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<div id="content" class="colM">
|
||||
<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 %}
|
||||
<div class="form-row">
|
||||
<label for="id_username">Username:</label> {{ form.username }}
|
||||
|
|
|
@ -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 *" % module)
|
||||
__test__[module] = module_doc or ""
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
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_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):
|
||||
"""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):
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from django.forms import ModelForm
|
||||
from django.contrib.auth.models import Group, User
|
||||
from djangorestframework.resources import ModelResource
|
||||
|
@ -7,18 +6,22 @@ from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
|||
from djangorestframework.tests.models import CustomUser
|
||||
from djangorestframework.tests.testcases import TestModelsTestCase
|
||||
|
||||
|
||||
class GroupResource(ModelResource):
|
||||
model = Group
|
||||
|
||||
|
||||
class UserForm(ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
exclude = ('last_login', 'date_joined')
|
||||
|
||||
|
||||
class UserResource(ModelResource):
|
||||
model = User
|
||||
form = UserForm
|
||||
|
||||
|
||||
class CustomUserResource(ModelResource):
|
||||
model = CustomUser
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ else:
|
|||
urlpatterns = patterns('',
|
||||
url(r'^$', oauth_required(ClientView.as_view())),
|
||||
url(r'^oauth/', include('oauth_provider.urls')),
|
||||
url(r'^accounts/login/$', 'djangorestframework.utils.staticviews.api_login'),
|
||||
url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -132,17 +132,18 @@
|
|||
# self.assertEqual(files['file1'].read(), 'blablabla')
|
||||
|
||||
from StringIO import StringIO
|
||||
from cgi import parse_qs
|
||||
from django import forms
|
||||
from django.test import TestCase
|
||||
from djangorestframework.parsers import FormParser
|
||||
from djangorestframework.parsers import XMLParser
|
||||
import datetime
|
||||
|
||||
|
||||
class Form(forms.Form):
|
||||
field1 = forms.CharField(max_length=3)
|
||||
field2 = forms.CharField()
|
||||
|
||||
|
||||
class TestFormParser(TestCase):
|
||||
def setUp(self):
|
||||
self.string = "field1=abc&field2=defghijk"
|
||||
|
@ -152,10 +153,11 @@ class TestFormParser(TestCase):
|
|||
parser = FormParser(None)
|
||||
|
||||
stream = StringIO(self.string)
|
||||
(data, files) = parser.parse(stream)
|
||||
(data, files) = parser.parse(stream, {}, [])
|
||||
|
||||
self.assertEqual(Form(data).is_valid(), True)
|
||||
|
||||
|
||||
class TestXMLParser(TestCase):
|
||||
def setUp(self):
|
||||
self._input = StringIO(
|
||||
|
@ -163,13 +165,13 @@ class TestXMLParser(TestCase):
|
|||
'<root>'
|
||||
'<field_a>121.0</field_a>'
|
||||
'<field_b>dasd</field_b>'
|
||||
'<field_c></field_c>'
|
||||
'<field_c></field_c>'
|
||||
'<field_d>2011-12-25 12:45:00</field_d>'
|
||||
'</root>'
|
||||
)
|
||||
self._data = {
|
||||
)
|
||||
self._data = {
|
||||
'field_a': 121,
|
||||
'field_b': 'dasd',
|
||||
'field_b': 'dasd',
|
||||
'field_c': None,
|
||||
'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
||||
}
|
||||
|
@ -183,21 +185,21 @@ class TestXMLParser(TestCase):
|
|||
'</sub_data_list>'
|
||||
'<name>name</name>'
|
||||
'</root>'
|
||||
)
|
||||
)
|
||||
self._complex_data = {
|
||||
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
|
||||
"name": "name",
|
||||
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
|
||||
"name": "name",
|
||||
"sub_data_list": [
|
||||
{
|
||||
"sub_id": 1,
|
||||
"sub_id": 1,
|
||||
"sub_name": "first"
|
||||
},
|
||||
},
|
||||
{
|
||||
"sub_id": 2,
|
||||
"sub_id": 2,
|
||||
"sub_name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def test_parse(self):
|
||||
parser = XMLParser(None)
|
||||
|
|
|
@ -1,21 +1,180 @@
|
|||
import re
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.compat import View as DjangoView
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.mixins import ResponseMixin
|
||||
from djangorestframework.views import View
|
||||
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||
XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
|
||||
from djangorestframework.parsers import JSONParser, YAMLParser, XMLParser
|
||||
from djangorestframework.parsers import YAMLParser, XMLParser
|
||||
|
||||
from StringIO import StringIO
|
||||
import datetime
|
||||
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"]}'
|
||||
_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)
|
||||
|
||||
|
||||
class JSONRendererTests(TestCase):
|
||||
"""
|
||||
Tests specific to the JSON Renderer
|
||||
|
@ -51,30 +211,16 @@ class JSONRendererTests(TestCase):
|
|||
content = renderer.render(obj, 'application/json; indent=2')
|
||||
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):
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return Response({'foo': ['bar', 'baz']})
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])),
|
||||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
|
||||
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
|
||||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
|
||||
)
|
||||
|
||||
|
||||
|
@ -149,22 +295,21 @@ if YAMLRenderer:
|
|||
self.assertEquals(obj, data)
|
||||
|
||||
|
||||
|
||||
class XMLRendererTestCase(TestCase):
|
||||
"""
|
||||
Tests specific to the XML Renderer
|
||||
"""
|
||||
|
||||
_complex_data = {
|
||||
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
|
||||
"name": "name",
|
||||
"creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
|
||||
"name": "name",
|
||||
"sub_data_list": [
|
||||
{
|
||||
"sub_id": 1,
|
||||
"sub_id": 1,
|
||||
"sub_name": "first"
|
||||
},
|
||||
},
|
||||
{
|
||||
"sub_id": 2,
|
||||
"sub_id": 2,
|
||||
"sub_name": "second"
|
||||
}
|
||||
]
|
||||
|
@ -219,12 +364,12 @@ class XMLRendererTestCase(TestCase):
|
|||
renderer = XMLRenderer(None)
|
||||
content = renderer.render({'field': None}, 'application/xml')
|
||||
self.assertXMLContains(content, '<field></field>')
|
||||
|
||||
|
||||
def test_render_complex_data(self):
|
||||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
renderer = XMLRenderer(None)
|
||||
content = renderer.render(self._complex_data, 'application/xml')
|
||||
self.assertXMLContains(content, '<sub_name>first</sub_name>')
|
||||
self.assertXMLContains(content, '<sub_name>second</sub_name>')
|
||||
|
@ -233,9 +378,9 @@ class XMLRendererTestCase(TestCase):
|
|||
"""
|
||||
Test XML rendering.
|
||||
"""
|
||||
renderer = XMLRenderer(None)
|
||||
renderer = XMLRenderer(None)
|
||||
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
|
||||
|
||||
|
||||
parser = XMLParser(None)
|
||||
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))
|
||||
|
@ -245,4 +390,3 @@ class XMLRendererTestCase(TestCase):
|
|||
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
|
||||
self.assertTrue(xml.endswith('</root>'))
|
||||
self.assertTrue(string in xml, '%r not in %r' % (string, xml))
|
||||
|
||||
|
|
|
@ -4,205 +4,214 @@ Tests for content parsing, and form-overloaded content parsing.
|
|||
from django.conf.urls.defaults import patterns
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, Client
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.authentication import UserLoggedInAuthentication
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.mixins import RequestMixin
|
||||
from djangorestframework.parsers import FormParser, MultiPartParser, \
|
||||
PlainTextParser, JSONParser
|
||||
from djangorestframework.utils import RequestFactory
|
||||
from djangorestframework.parsers import (
|
||||
FormParser,
|
||||
MultiPartParser,
|
||||
PlainTextParser,
|
||||
JSONParser
|
||||
)
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework.views import View
|
||||
|
||||
|
||||
class RequestTestCase(TestCase):
|
||||
|
||||
def build_request(self, method, *args, **kwargs):
|
||||
factory = RequestFactory()
|
||||
method = getattr(factory, method)
|
||||
original_request = method(*args, **kwargs)
|
||||
return Request(original_request)
|
||||
factory = RequestFactory()
|
||||
|
||||
|
||||
class TestMethodOverloading(RequestTestCase):
|
||||
|
||||
def test_standard_behaviour_determines_GET(self):
|
||||
"""GET requests identified"""
|
||||
request = self.build_request('get', '/')
|
||||
class TestMethodOverloading(TestCase):
|
||||
def test_GET_method(self):
|
||||
"""
|
||||
GET requests identified.
|
||||
"""
|
||||
request = factory.get('/')
|
||||
self.assertEqual(request.method, 'GET')
|
||||
|
||||
def test_standard_behaviour_determines_POST(self):
|
||||
"""POST requests identified"""
|
||||
request = self.build_request('post', '/')
|
||||
def test_POST_method(self):
|
||||
"""
|
||||
POST requests identified.
|
||||
"""
|
||||
request = factory.post('/')
|
||||
self.assertEqual(request.method, 'POST')
|
||||
|
||||
def test_overloaded_POST_behaviour_determines_overloaded_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'})
|
||||
self.assertEqual(request.method, 'DELETE')
|
||||
|
||||
def test_HEAD_is_a_valid_method(self):
|
||||
"""HEAD requests identified"""
|
||||
request = request = self.build_request('head', '/')
|
||||
def test_HEAD_method(self):
|
||||
"""
|
||||
HEAD requests identified.
|
||||
"""
|
||||
request = factory.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):
|
||||
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
|
||||
|
||||
class TestContentParsing(TestCase):
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_standard_behaviour_determines_form_content_POST(self):
|
||||
"""Ensure request.DATA returns content for POST request with form content."""
|
||||
form_data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser(), MultiPartParser())
|
||||
|
||||
request = self.build_request('post', '/', data=form_data, parsers=parsers)
|
||||
self.assertEqual(request.DATA.items(), form_data.items())
|
||||
"""
|
||||
Ensure request.DATA returns content for POST request with form content.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser, MultiPartParser)
|
||||
request = factory.post('/', data, parser=parsers)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
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_type = 'text/plain'
|
||||
parsers = (PlainTextParser(),)
|
||||
|
||||
request = self.build_request('post', '/', content, content_type=content_type, parsers=parsers)
|
||||
parsers = (PlainTextParser,)
|
||||
request = factory.post('/', content, content_type=content_type,
|
||||
parsers=parsers)
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
def test_standard_behaviour_determines_form_content_PUT(self):
|
||||
"""Ensure request.DATA returns content for PUT request with form content."""
|
||||
form_data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser(), MultiPartParser())
|
||||
|
||||
request = self.build_request('put', '/', data=form_data, parsers=parsers)
|
||||
self.assertEqual(request.DATA.items(), form_data.items())
|
||||
"""
|
||||
Ensure request.DATA returns content for PUT request with form content.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser, MultiPartParser)
|
||||
request = factory.put('/', data, parsers=parsers)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
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_type = 'text/plain'
|
||||
parsers = (PlainTextParser(),)
|
||||
|
||||
request = self.build_request('put', '/', content, content_type=content_type, parsers=parsers)
|
||||
parsers = (PlainTextParser, )
|
||||
request = factory.put('/', content, content_type=content_type,
|
||||
parsers=parsers)
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
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_type = 'text/plain'
|
||||
form_data = {Request._CONTENT_PARAM: content,
|
||||
Request._CONTENTTYPE_PARAM: content_type}
|
||||
parsers = (PlainTextParser(),)
|
||||
|
||||
request = self.build_request('post', '/', form_data, parsers=parsers)
|
||||
data = {
|
||||
Request._CONTENT_PARAM: content,
|
||||
Request._CONTENTTYPE_PARAM: content_type
|
||||
}
|
||||
parsers = (PlainTextParser, )
|
||||
request = factory.post('/', data, parsers=parsers)
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
def test_accessing_post_after_data_form(self):
|
||||
"""Ensures request.POST can be accessed after request.DATA in form request"""
|
||||
form_data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser(), MultiPartParser())
|
||||
|
||||
request = self.build_request('post', '/', data=form_data)
|
||||
self.assertEqual(request.DATA.items(), form_data.items())
|
||||
self.assertEqual(request.POST.items(), form_data.items())
|
||||
"""
|
||||
Ensures request.POST can be accessed after request.DATA in
|
||||
form request.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
request = factory.post('/', data=data)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
self.assertEqual(request.POST.items(), data.items())
|
||||
|
||||
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'}
|
||||
content = json.dumps(data)
|
||||
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.POST.items(), [])
|
||||
|
||||
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'}
|
||||
content = json.dumps(data)
|
||||
content_type = 'application/json'
|
||||
parsers = (JSONParser(),)
|
||||
parsers = (JSONParser, )
|
||||
form_data = {Request._CONTENT_PARAM: content,
|
||||
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.POST.items(), form_data.items())
|
||||
|
||||
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)
|
||||
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.DATA.items(), form_data.items())
|
||||
self.assertEqual(request.POST.items(), data.items())
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
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'}
|
||||
content = json.dumps(data)
|
||||
content_type = 'application/json'
|
||||
parsers = (JSONParser(),)
|
||||
|
||||
request = self.build_request('post', '/', content, content_type=content_type, parsers=parsers)
|
||||
post_items = request.POST.items()
|
||||
|
||||
self.assertEqual(len(post_items), 1)
|
||||
self.assertEqual(len(post_items[0]), 2)
|
||||
self.assertEqual(post_items[0][0], content)
|
||||
parsers = (JSONParser, )
|
||||
request = factory.post('/', content, content_type=content_type,
|
||||
parsers=parsers)
|
||||
self.assertEqual(request.POST.items(), [])
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
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'}
|
||||
content = json.dumps(data)
|
||||
content_type = 'application/json'
|
||||
parsers = (JSONParser(),)
|
||||
parsers = (JSONParser, )
|
||||
form_data = {Request._CONTENT_PARAM: content,
|
||||
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.DATA.items(), data.items())
|
||||
|
||||
|
||||
class MockView(View):
|
||||
authentication = (UserLoggedInAuthentication,)
|
||||
|
||||
def post(self, request):
|
||||
if request.POST.get('example') is not None:
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
@ -223,17 +232,19 @@ class TestContentParsingWithAuthentication(TestCase):
|
|||
self.email = 'lennon@thebeatles.com'
|
||||
self.password = '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):
|
||||
"""Ensures request.POST exists after UserLoggedInAuthentication when user doesn't log in"""
|
||||
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.
|
||||
"""
|
||||
content = {'example': 'example'}
|
||||
|
||||
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)
|
||||
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):
|
||||
# """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import json
|
||||
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 djangorestframework.response import Response, ImmediateResponse
|
||||
from djangorestframework.mixins import ResponseMixin
|
||||
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 import status
|
||||
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||
XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
|
||||
from djangorestframework.renderers import (
|
||||
BaseRenderer,
|
||||
JSONRenderer,
|
||||
DocumentingHTMLRenderer,
|
||||
DEFAULT_RENDERERS
|
||||
)
|
||||
|
||||
|
||||
class TestResponseDetermineRenderer(TestCase):
|
||||
|
@ -20,7 +21,7 @@ class TestResponseDetermineRenderer(TestCase):
|
|||
def get_response(self, url='', accept_list=[], renderers=[]):
|
||||
kwargs = {}
|
||||
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)
|
||||
return Response(request=request, renderers=renderers)
|
||||
|
||||
|
@ -43,7 +44,7 @@ class TestResponseDetermineRenderer(TestCase):
|
|||
"""
|
||||
response = self.get_response(accept_list=None)
|
||||
self.assertEqual(response._determine_accept_list(), ['*/*'])
|
||||
|
||||
|
||||
def test_determine_accept_list_overriden_header(self):
|
||||
"""
|
||||
Test Accept header overriding.
|
||||
|
@ -81,7 +82,7 @@ class TestResponseDetermineRenderer(TestCase):
|
|||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, '*/*')
|
||||
self.assertTrue(renderer, prenderer)
|
||||
|
||||
|
||||
def test_determine_renderer_no_renderer(self):
|
||||
"""
|
||||
Test determine renderer when no renderer can satisfy the Accept list.
|
||||
|
@ -94,14 +95,14 @@ class TestResponseDetermineRenderer(TestCase):
|
|||
|
||||
|
||||
class TestResponseRenderContent(TestCase):
|
||||
|
||||
|
||||
def get_response(self, url='', accept_list=[], content=None):
|
||||
request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
|
||||
return Response(request=request, content=content, renderers=[r() for r in DEFAULT_RENDERERS])
|
||||
|
||||
def test_render(self):
|
||||
"""
|
||||
Test rendering simple data to json.
|
||||
Test rendering simple data to json.
|
||||
"""
|
||||
content = {'a': 1, 'b': [1, 2, 3]}
|
||||
content_type = 'application/json'
|
||||
|
@ -134,34 +135,33 @@ class RendererB(BaseRenderer):
|
|||
return RENDERER_B_SERIALIZER(obj)
|
||||
|
||||
|
||||
class MockView(ResponseMixin, DjangoView):
|
||||
renderer_classes = (RendererA, RendererB)
|
||||
class MockView(View):
|
||||
renderers = (RendererA, RendererB)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||
self.response = self.prepare_response(response)
|
||||
return self.response
|
||||
return Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||
|
||||
|
||||
class HTMLView(View):
|
||||
renderer_classes = (DocumentingHTMLRenderer, )
|
||||
renderers = (DocumentingHTMLRenderer, )
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
class HTMLView1(View):
|
||||
renderer_classes = (DocumentingHTMLRenderer, JSONRenderer)
|
||||
renderers = (DocumentingHTMLRenderer, JSONRenderer)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
return Response('text')
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
|
||||
url(r'^html$', HTMLView.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.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):
|
||||
"""
|
||||
|
@ -275,10 +268,10 @@ class Issue122Tests(TestCase):
|
|||
"""
|
||||
Test if no infinite recursion occurs.
|
||||
"""
|
||||
resp = self.client.get('/html')
|
||||
|
||||
self.client.get('/html')
|
||||
|
||||
def test_html_renderer_is_first(self):
|
||||
"""
|
||||
Test if no infinite recursion occurs.
|
||||
"""
|
||||
resp = self.client.get('/html1')
|
||||
self.client.get('/html1')
|
||||
|
|
|
@ -16,7 +16,8 @@ class MyView(View):
|
|||
renderers = (JSONRenderer, )
|
||||
|
||||
def get(self, request):
|
||||
return Response(reverse('another', request))
|
||||
return Response(reverse('myview', request=request))
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^myview$', MyView.as_view(), name='myview'),
|
||||
|
|
|
@ -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.test import TestCase
|
||||
from django.test import Client
|
||||
from django import forms
|
||||
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.views import ListOrCreateModelView, InstanceModelView
|
||||
|
||||
from StringIO import StringIO
|
||||
from djangorestframework.views import (
|
||||
View,
|
||||
ListOrCreateModelView,
|
||||
InstanceModelView
|
||||
)
|
||||
|
||||
|
||||
class MockView(View):
|
||||
|
@ -24,6 +25,7 @@ class MockViewFinal(View):
|
|||
def final(self, request, response, *args, **kwargs):
|
||||
return HttpResponse('{"test": "passed"}', content_type="application/json")
|
||||
|
||||
|
||||
class ResourceMockView(View):
|
||||
"""This is a resource-based mock view"""
|
||||
|
||||
|
@ -34,6 +36,7 @@ class ResourceMockView(View):
|
|||
|
||||
form = MockForm
|
||||
|
||||
|
||||
class MockResource(ModelResource):
|
||||
"""This is a mock model-based resource"""
|
||||
|
||||
|
@ -45,16 +48,16 @@ class MockResource(ModelResource):
|
|||
model = MockResourceModel
|
||||
fields = ('foo', 'bar', 'baz')
|
||||
|
||||
urlpatterns = patterns('djangorestframework.utils.staticviews',
|
||||
url(r'^accounts/login$', 'api_login'),
|
||||
url(r'^accounts/logout$', 'api_logout'),
|
||||
urlpatterns = patterns('',
|
||||
url(r'^mock/$', MockView.as_view()),
|
||||
url(r'^mock/final/$', MockViewFinal.as_view()),
|
||||
url(r'^resourcemock/$', ResourceMockView.as_view()),
|
||||
url(r'^model/$', ListOrCreateModelView.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):
|
||||
"""Test the base view class of djangorestframework"""
|
||||
urls = 'djangorestframework.tests.views'
|
||||
|
@ -62,8 +65,7 @@ class BaseViewTests(TestCase):
|
|||
def test_view_call_final(self):
|
||||
response = self.client.options('/mock/final/')
|
||||
self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
|
||||
parser = JSONParser(None)
|
||||
(data, files) = parser.parse(StringIO(response.content))
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data['test'], 'passed')
|
||||
|
||||
def test_options_method_simple_view(self):
|
||||
|
@ -77,9 +79,9 @@ class BaseViewTests(TestCase):
|
|||
self._verify_options_response(response,
|
||||
name='Resource Mock',
|
||||
description='This is a resource-based mock view',
|
||||
fields={'foo':'BooleanField',
|
||||
'bar':'IntegerField',
|
||||
'baz':'CharField',
|
||||
fields={'foo': 'BooleanField',
|
||||
'bar': 'IntegerField',
|
||||
'baz': 'CharField',
|
||||
})
|
||||
|
||||
def test_options_method_model_resource_list_view(self):
|
||||
|
@ -87,9 +89,9 @@ class BaseViewTests(TestCase):
|
|||
self._verify_options_response(response,
|
||||
name='Mock List',
|
||||
description='This is a mock model-based resource',
|
||||
fields={'foo':'BooleanField',
|
||||
'bar':'IntegerField',
|
||||
'baz':'CharField',
|
||||
fields={'foo': 'BooleanField',
|
||||
'bar': 'IntegerField',
|
||||
'baz': 'CharField',
|
||||
})
|
||||
|
||||
def test_options_method_model_resource_detail_view(self):
|
||||
|
@ -97,17 +99,16 @@ class BaseViewTests(TestCase):
|
|||
self._verify_options_response(response,
|
||||
name='Mock Instance',
|
||||
description='This is a mock model-based resource',
|
||||
fields={'foo':'BooleanField',
|
||||
'bar':'IntegerField',
|
||||
'baz':'CharField',
|
||||
fields={'foo': 'BooleanField',
|
||||
'bar': 'IntegerField',
|
||||
'baz': 'CharField',
|
||||
})
|
||||
|
||||
def _verify_options_response(self, response, name, description, fields=None, status=200,
|
||||
mime_type='application/json'):
|
||||
self.assertEqual(response.status_code, status)
|
||||
self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
|
||||
parser = JSONParser(None)
|
||||
(data, files) = parser.parse(StringIO(response.content))
|
||||
data = json.loads(response.content)
|
||||
self.assertTrue('application/json' in data['renders'])
|
||||
self.assertEqual(name, data['name'])
|
||||
self.assertEqual(description, data['description'])
|
||||
|
@ -123,15 +124,12 @@ class ExtraViewsTests(TestCase):
|
|||
|
||||
def test_login_view(self):
|
||||
"""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['Content-Type'].split(';')[0], 'text/html')
|
||||
|
||||
def test_logout_view(self):
|
||||
"""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['Content-Type'].split(';')[0], 'text/html')
|
||||
|
||||
# TODO: Add login/logout behaviour tests
|
||||
|
||||
|
|
|
@ -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'),
|
||||
(r'^accounts/logout/$', 'api_logout'),
|
||||
|
||||
template_name = {'template_name': 'djangorestframework/login.html'}
|
||||
|
||||
urlpatterns = patterns('django.contrib.auth.views',
|
||||
url(r'^login/$', 'login', template_name, name='login'),
|
||||
url(r'^logout/$', 'logout', template_name, name='logout'),
|
||||
)
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
import django
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.xmlutils import SimplerXMLGenerator
|
||||
from django.core.urlresolvers import resolve, reverse as django_reverse
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import resolve
|
||||
|
||||
from djangorestframework.compat import StringIO
|
||||
from djangorestframework.compat import RequestFactory as DjangoRequestFactory
|
||||
from djangorestframework.request import Request
|
||||
|
||||
import re
|
||||
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 )')
|
||||
|
||||
|
||||
def as_tuple(obj):
|
||||
"""
|
||||
Given an object which may be a list/tuple, another object, or None,
|
||||
|
@ -49,45 +43,6 @@ def url_resolves(url):
|
|||
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
|
||||
class XML2Dict(object):
|
||||
|
||||
|
@ -99,24 +54,23 @@ class XML2Dict(object):
|
|||
# Save attrs and text, hope there will not be a child with same name
|
||||
if node.text:
|
||||
node_tree = node.text
|
||||
for (k,v) in node.attrib.items():
|
||||
k,v = self._namespace_split(k, v)
|
||||
for (k, v) in node.attrib.items():
|
||||
k, v = self._namespace_split(k, v)
|
||||
node_tree[k] = v
|
||||
#Save childrens
|
||||
for child in node.getchildren():
|
||||
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
|
||||
continue
|
||||
old = node_tree[tag]
|
||||
if not isinstance(old, list):
|
||||
node_tree.pop(tag)
|
||||
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] = [old] # multi times, so change old dict to a list
|
||||
node_tree[tag].append(tree) # add the new one
|
||||
|
||||
return node_tree
|
||||
|
||||
|
||||
def _namespace_split(self, tag, value):
|
||||
"""
|
||||
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
|
||||
|
@ -179,23 +133,41 @@ class XMLRenderer():
|
|||
xml.endDocument()
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def 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
|
||||
*request* to build a fully qualified URL.
|
||||
Replicate RequestFactory, but return Request, not HttpRequest.
|
||||
"""
|
||||
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):
|
||||
from django.core.urlresolvers import reverse_lazy as django_reverse_lazy
|
||||
def post(self, *args, **kwargs):
|
||||
parsers = kwargs.pop('parsers', None)
|
||||
request = super(RequestFactory, self).post(*args, **kwargs)
|
||||
return Request(request, parsers)
|
||||
|
||||
def reverse_lazy(viewname, request, *args, **kwargs):
|
||||
"""
|
||||
Do the same as :py:func:`django.core.urlresolvers.reverse_lazy` but using
|
||||
*request* to build a fully qualified URL.
|
||||
"""
|
||||
return request.build_absolute_uri(django_reverse_lazy(viewname, *args, **kwargs))
|
||||
def put(self, *args, **kwargs):
|
||||
parsers = kwargs.pop('parsers', None)
|
||||
request = super(RequestFactory, self).put(*args, **kwargs)
|
||||
return Request(request, parsers)
|
||||
|
||||
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)
|
||||
|
|
|
@ -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)
|
|
@ -6,15 +6,13 @@ By setting or modifying class attributes on your view, you change it's predefine
|
|||
"""
|
||||
|
||||
import re
|
||||
from django.core.urlresolvers import set_script_prefix, get_script_prefix
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
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.utils import allowed_methods
|
||||
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.
|
||||
"""
|
||||
|
||||
renderer_classes = renderers.DEFAULT_RENDERERS
|
||||
renderers = renderers.DEFAULT_RENDERERS
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
@ -118,7 +116,15 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
|
@ -183,32 +189,35 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
|
||||
def initial(self, request, *args, **kargs):
|
||||
"""
|
||||
Returns an `HttpRequest`. This method is a hook for any code that needs to run
|
||||
prior to anything else.
|
||||
Required if you want to do things like set `request.upload_handlers` before
|
||||
the authentication and dispatch handling is run.
|
||||
This method is a hook for any code that needs to run prior to
|
||||
anything else.
|
||||
Required if you want to do things like set `request.upload_handlers`
|
||||
before the authentication and dispatch handling is run.
|
||||
"""
|
||||
pass
|
||||
|
||||
def final(self, request, response, *args, **kargs):
|
||||
"""
|
||||
Returns an `HttpResponse`. This method is a hook for any code that needs to run
|
||||
after everything else in the view.
|
||||
This method is a hook for any code that needs to run after everything
|
||||
else in the view.
|
||||
Returns the final response object.
|
||||
"""
|
||||
# Always add these headers.
|
||||
response['Allow'] = ', '.join(allowed_methods(self))
|
||||
# sample to allow caching using Vary http header
|
||||
response['Vary'] = 'Authenticate, Accept'
|
||||
|
||||
response.view = self
|
||||
response.request = request
|
||||
response.renderers = self.renderers
|
||||
for key, value in self.headers.items():
|
||||
response[key] = value
|
||||
return response
|
||||
|
||||
# Note: session based authentication is explicitly CSRF validated,
|
||||
# all other authentication is CSRF exempt.
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.request = self.create_request(request)
|
||||
request = self.create_request(request)
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.headers = self.default_response_headers
|
||||
|
||||
try:
|
||||
self.initial(request, *args, **kwargs)
|
||||
|
@ -222,26 +231,17 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
else:
|
||||
handler = self.http_method_not_allowed
|
||||
|
||||
# TODO: should we enforce HttpResponse, like Django does ?
|
||||
response = handler(request, *args, **kwargs)
|
||||
|
||||
# Prepare response for the response cycle.
|
||||
self.response = response = self.prepare_response(response)
|
||||
|
||||
# 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'):
|
||||
if isinstance(response, Response):
|
||||
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
|
||||
response.raw_content = self.filter_response(response.raw_content)
|
||||
else:
|
||||
response.content = self.filter_response(response.content)
|
||||
|
||||
except ImmediateResponse, response:
|
||||
# Prepare response for the response cycle.
|
||||
self.response = response = self.prepare_response(response)
|
||||
except ImmediateResponse, exc:
|
||||
response = exc.response
|
||||
|
||||
# `final` is the last opportunity to temper with the response, or even
|
||||
# completely replace it.
|
||||
return self.final(request, response, *args, **kwargs)
|
||||
self.response = self.final(request, response, *args, **kwargs)
|
||||
return self.response
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
content = {
|
||||
|
@ -266,7 +266,7 @@ class ModelView(View):
|
|||
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.
|
||||
"""
|
||||
|
|
|
@ -49,20 +49,20 @@ YAML
|
|||
|
||||
YAML support is optional, and requires `PyYAML`_.
|
||||
|
||||
|
||||
Login / Logout
|
||||
--------------
|
||||
|
||||
Django REST framework includes login and logout views that are useful if
|
||||
you're using the self-documenting API::
|
||||
Django REST framework includes login and logout views that are needed if
|
||||
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',
|
||||
# Add your resources here
|
||||
(r'^accounts/login/$', 'api_login'),
|
||||
(r'^accounts/logout/$', 'api_logout'),
|
||||
)
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('',
|
||||
...
|
||||
url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
)
|
||||
|
||||
.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
|
||||
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/
|
||||
|
|
|
@ -64,6 +64,12 @@ To add Django REST framework to a Django project:
|
|||
|
||||
* Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``.
|
||||
* 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.
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.db import models
|
|||
from django.template.defaultfilters import slugify
|
||||
import uuid
|
||||
|
||||
|
||||
def uuid_str():
|
||||
return str(uuid.uuid1())
|
||||
|
||||
|
@ -14,6 +15,7 @@ RATING_CHOICES = ((0, 'Awful'),
|
|||
|
||||
MAX_POSTS = 10
|
||||
|
||||
|
||||
class BlogPost(models.Model):
|
||||
key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False)
|
||||
title = models.CharField(max_length=128)
|
||||
|
@ -37,4 +39,3 @@ class Comment(models.Model):
|
|||
comment = models.TextField()
|
||||
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)
|
||||
|
||||
|
|
|
@ -11,8 +11,15 @@ class BlogPostResource(ModelResource):
|
|||
fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
|
||||
ordering = ('-created',)
|
||||
|
||||
def url(self, instance):
|
||||
return reverse('blog-post',
|
||||
kwargs={'key': instance.key},
|
||||
request=self.request)
|
||||
|
||||
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):
|
||||
|
@ -24,4 +31,6 @@ class CommentResource(ModelResource):
|
|||
ordering = ('-created',)
|
||||
|
||||
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)
|
||||
|
|
|
@ -10,11 +10,12 @@ from django.conf.urls.defaults import patterns, url
|
|||
class ExampleView(ResponseMixin, View):
|
||||
"""An example view using Django 1.3's class based views.
|
||||
Uses djangorestframework's RendererMixin to provide support for multiple output formats."""
|
||||
renderer_classes = DEFAULT_RENDERERS
|
||||
renderers = DEFAULT_RENDERERS
|
||||
|
||||
def get(self, request):
|
||||
url = reverse('mixin-view', request)
|
||||
response = Response({'description': 'Some example content',
|
||||
'url': reverse('mixin-view', request)}, status=200)
|
||||
'url': url}, status=200)
|
||||
self.response = self.prepare_response(response)
|
||||
return self.response
|
||||
|
||||
|
@ -22,4 +23,3 @@ class ExampleView(ResponseMixin, View):
|
|||
urlpatterns = patterns('',
|
||||
url(r'^$', ExampleView.as_view(), name='mixin-view'),
|
||||
)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.db import models
|
|||
|
||||
MAX_INSTANCES = 10
|
||||
|
||||
|
||||
class MyModel(models.Model):
|
||||
foo = models.BooleanField()
|
||||
bar = models.IntegerField(help_text='Must be an integer.')
|
||||
|
@ -15,5 +16,3 @@ class MyModel(models.Model):
|
|||
super(MyModel, self).save(*args, **kwargs)
|
||||
while MyModel.objects.all().count() > MAX_INSTANCES:
|
||||
MyModel.objects.all().order_by('-created')[0].delete()
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
from djangorestframework.resources import ModelResource
|
||||
from djangorestframework.reverse import reverse
|
||||
from modelresourceexample.models import MyModel
|
||||
|
||||
|
||||
class MyModelResource(ModelResource):
|
||||
model = MyModel
|
||||
fields = ('foo', 'bar', 'baz', 'url')
|
||||
ordering = ('created',)
|
||||
|
||||
def url(self, instance):
|
||||
return reverse('model-resource-instance',
|
||||
kwargs={'id': instance.id},
|
||||
request=self.request)
|
||||
|
|
|
@ -2,7 +2,10 @@ from django.conf.urls.defaults import patterns, url
|
|||
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
||||
from modelresourceexample.resources import MyModelResource
|
||||
|
||||
my_model_list = ListOrCreateModelView.as_view(resource=MyModelResource)
|
||||
my_model_instance = InstanceModelView.as_view(resource=MyModelResource)
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', ListOrCreateModelView.as_view(resource=MyModelResource), name='model-resource-root'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', InstanceModelView.as_view(resource=MyModelResource)),
|
||||
url(r'^$', my_model_list, name='model-resource-root'),
|
||||
url(r'^(?P<id>[0-9]+)/$', my_model_instance, name='model-resource-instance'),
|
||||
)
|
||||
|
|
|
@ -28,6 +28,20 @@ def remove_oldest_files(dir, 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):
|
||||
"""
|
||||
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)
|
||||
"""
|
||||
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],
|
||||
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):
|
||||
"""
|
||||
Create a new stored object, with a unique key.
|
||||
"""
|
||||
key = str(uuid.uuid1())
|
||||
pathname = os.path.join(OBJECT_STORE_DIR, key)
|
||||
pickle.dump(self.CONTENT, open(pathname, 'wb'))
|
||||
filename = get_filename(key)
|
||||
pickle.dump(self.CONTENT, open(filename, 'wb'))
|
||||
|
||||
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})
|
||||
|
||||
|
||||
|
@ -60,30 +79,31 @@ class StoredObject(View):
|
|||
Represents a stored object.
|
||||
The object may be any picklable content.
|
||||
"""
|
||||
|
||||
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)
|
||||
if not os.path.exists(pathname):
|
||||
filename = get_filename(key)
|
||||
if not os.path.exists(filename):
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
pickle.dump(self.CONTENT, open(pathname, 'wb'))
|
||||
filename = get_filename(key)
|
||||
pickle.dump(self.CONTENT, open(filename, 'wb'))
|
||||
return Response(self.CONTENT)
|
||||
|
||||
def delete(self, request, key):
|
||||
"""
|
||||
Delete a stored object, by removing it's pickled file.
|
||||
"""
|
||||
pathname = os.path.join(OBJECT_STORE_DIR, key)
|
||||
if not os.path.exists(pathname):
|
||||
filename = get_filename(key)
|
||||
if not os.path.exists(filename):
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
os.remove(pathname)
|
||||
os.remove(filename)
|
||||
return Response()
|
||||
|
|
|
@ -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()])
|
||||
STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles()))
|
||||
|
||||
|
||||
class PygmentsForm(forms.Form):
|
||||
"""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.
|
||||
|
@ -24,5 +25,3 @@ class PygmentsForm(forms.Form):
|
|||
initial='python')
|
||||
style = forms.ChoiceField(choices=STYLE_CHOICES,
|
||||
initial='friendly')
|
||||
|
||||
|
||||
|
|
|
@ -14,13 +14,13 @@ class TestPygmentsExample(TestCase):
|
|||
self.factory = RequestFactory()
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
views.HIGHLIGHTED_CODE_DIR = self.temp_dir
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
shutil.rmtree(self.temp_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_get_to_root(self):
|
||||
'''Just do a get on the base url'''
|
||||
request = self.factory.get('/pygments')
|
||||
|
@ -44,6 +44,3 @@ class TestPygmentsExample(TestCase):
|
|||
response = view(request)
|
||||
response_locations = json.loads(response.content)
|
||||
self.assertEquals(locations, response_locations)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import with_statement # for python 2.5
|
||||
from django.conf import settings
|
||||
|
||||
from djangorestframework.resources import FormResource
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.renderers import BaseRenderer
|
||||
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
|
||||
"""
|
||||
filepaths = [os.path.join(dir, file) for file in os.listdir(dir) if not file.startswith('.')]
|
||||
return [item[0] for item in sorted( [(path, os.path.getctime(path)) for path in filepaths],
|
||||
key=operator.itemgetter(1), reverse=False) ]
|
||||
filepaths = [os.path.join(dir, file)
|
||||
for file in os.listdir(dir)
|
||||
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):
|
||||
"""
|
||||
|
@ -60,8 +63,11 @@ class PygmentsRoot(View):
|
|||
"""
|
||||
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)]
|
||||
return Response([reverse('pygments-instance', request, args=[unique_id]) for unique_id in unique_ids])
|
||||
unique_ids = [os.path.split(f)[1]
|
||||
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):
|
||||
"""
|
||||
|
@ -81,7 +87,7 @@ class PygmentsRoot(View):
|
|||
|
||||
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})
|
||||
|
||||
|
||||
|
@ -90,7 +96,7 @@ class PygmentsInstance(View):
|
|||
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.
|
||||
"""
|
||||
renderer_classes = (HTMLRenderer,)
|
||||
renderers = (HTMLRenderer, )
|
||||
|
||||
def get(self, request, unique_id):
|
||||
"""
|
||||
|
@ -110,4 +116,3 @@ class PygmentsInstance(View):
|
|||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
os.remove(pathname)
|
||||
return Response()
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class MyBaseViewUsingEnhancedRequest(RequestMixin, View):
|
|||
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):
|
||||
self.request = request = self.create_request(request)
|
||||
|
@ -41,4 +41,3 @@ class EchoRequestContentView(MyBaseViewUsingEnhancedRequest):
|
|||
def put(self, request, *args, **kwargs):
|
||||
return HttpResponse(("Found %s in request.DATA, content : %s" %
|
||||
(type(request.DATA), request.DATA)))
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django import forms
|
||||
|
||||
|
||||
class MyForm(forms.Form):
|
||||
foo = forms.BooleanField(required=False)
|
||||
bar = forms.IntegerField(help_text='Must be an integer.')
|
||||
|
|
|
@ -16,9 +16,11 @@ class ExampleView(View):
|
|||
Handle GET requests, returning a list of URLs pointing to
|
||||
three other views.
|
||||
"""
|
||||
urls = [reverse('another-example', request, kwargs={'num': num})
|
||||
for num in range(3)]
|
||||
return Response({"Some other resources": urls})
|
||||
resource_urls = [reverse('another-example',
|
||||
kwargs={'num': num},
|
||||
request=request)
|
||||
for num in range(3)]
|
||||
return Response({"Some other resources": resource_urls})
|
||||
|
||||
|
||||
class AnotherExampleView(View):
|
||||
|
|
|
@ -19,7 +19,7 @@ class Sandbox(View):
|
|||
For example, to get the default representation using curl:
|
||||
|
||||
bash: curl -X GET http://rest.ep.io/
|
||||
|
||||
|
||||
Or, to get the plaintext documentation represention:
|
||||
|
||||
bash: curl -X GET http://rest.ep.io/ -H 'Accept: text/plain'
|
||||
|
@ -49,19 +49,19 @@ class Sandbox(View):
|
|||
def get(self, request):
|
||||
return Response([
|
||||
{'name': 'Simple Resource example',
|
||||
'url': reverse('example-resource', request)},
|
||||
'url': reverse('example-resource', request=request)},
|
||||
{'name': 'Simple ModelResource example',
|
||||
'url': reverse('model-resource-root', request)},
|
||||
'url': reverse('model-resource-root', request=request)},
|
||||
{'name': 'Simple Mixin-only example',
|
||||
'url': reverse('mixin-view', request)},
|
||||
{'name': 'Object store API'
|
||||
'url': reverse('object-store-root', request)},
|
||||
'url': reverse('mixin-view', request=request)},
|
||||
{'name': 'Object store API',
|
||||
'url': reverse('object-store-root', request=request)},
|
||||
{'name': 'Code highlighting API',
|
||||
'url': reverse('pygments-root', request)},
|
||||
'url': reverse('pygments-root', request=request)},
|
||||
{'name': 'Blog posts API',
|
||||
'url': reverse('blog-posts-root', request)},
|
||||
'url': reverse('blog-posts-root', request=request)},
|
||||
{'name': 'Permissions example',
|
||||
'url': reverse('permissions-example', request)},
|
||||
'url': reverse('permissions-example', request=request)},
|
||||
{'name': 'Simple request mixin example',
|
||||
'url': reverse('request-example', request)}
|
||||
'url': reverse('request-example', request=request)}
|
||||
])
|
||||
|
|
|
@ -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
|
||||
try:
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
@ -15,9 +15,7 @@ urlpatterns = patterns('',
|
|||
(r'^pygments/', include('pygments_api.urls')),
|
||||
(r'^blog-post/', include('blogpost.urls')),
|
||||
(r'^permissions-example/', include('permissionsexample.urls')),
|
||||
(r'^request-example/', include('requestexample.urls')),
|
||||
|
||||
(r'^', include('djangorestframework.urls')),
|
||||
url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
|
||||
)
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
|
|
Loading…
Reference in New Issue
Block a user