More refactoring - move various less core stuff into utils etc

This commit is contained in:
Tom Christie 2011-04-29 14:32:56 +01:00
parent 93aa065fa9
commit b358fbdbe9
18 changed files with 203 additions and 204 deletions

View File

@ -1,8 +1,8 @@
"""The :mod:`authenticators` modules provides for pluggable authentication behaviour. """The :mod:`authentication` modules provides for pluggable authentication behaviour.
Authentication behaviour is provided by adding the mixin class :class:`AuthenticatorMixin` to a :class:`.Resource` or Django :class:`View` class. Authentication behaviour is provided by adding the mixin class :class:`AuthenticatorMixin` to a :class:`.Resource` or Django :class:`View` class.
The set of authenticators which are use is then specified by setting the :attr:`authenticators` attribute on the class, and listing a set of authenticator classes. The set of authentication which are use is then specified by setting the :attr:`authentication` attribute on the class, and listing a set of authentication classes.
""" """
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.middleware.csrf import CsrfViewMiddleware from django.middleware.csrf import CsrfViewMiddleware
@ -11,11 +11,11 @@ import base64
class BaseAuthenticator(object): class BaseAuthenticator(object):
"""All authenticators should extend BaseAuthenticator.""" """All authentication should extend BaseAuthenticator."""
def __init__(self, view): def __init__(self, view):
"""Initialise the authenticator with the mixin instance as state, """Initialise the authentication with the mixin instance as state,
in case the authenticator needs to access any metadata on the mixin object.""" in case the authentication needs to access any metadata on the mixin object."""
self.view = view self.view = view
def authenticate(self, request): def authenticate(self, request):

View File

@ -1,9 +1,24 @@
"""Compatability module to provide support for backwards compatability with older versions of django/python""" """Compatability module to provide support for backwards compatability with older versions of django/python"""
# cStringIO only if it's available
try:
import cStringIO as StringIO
except ImportError:
import StringIO
# parse_qs
try:
# python >= ?
from urlparse import parse_qs
except ImportError:
# python <= ?
from cgi import parse_qs
# django.test.client.RequestFactory (Django >= 1.3) # django.test.client.RequestFactory (Django >= 1.3)
try: try:
from django.test.client import RequestFactory from django.test.client import RequestFactory
except ImportError: except ImportError:
from django.test import Client from django.test import Client
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
@ -49,7 +64,7 @@ except ImportError:
# django.views.generic.View (Django >= 1.3) # django.views.generic.View (Django >= 1.3)
try: try:
from django.views.generic import View from django.views.generic import View
except: except ImportError:
from django import http from django import http
from django.utils.functional import update_wrapper from django.utils.functional import update_wrapper
# from django.utils.log import getLogger # from django.utils.log import getLogger
@ -127,10 +142,47 @@ except:
#) #)
return http.HttpResponseNotAllowed(allowed_methods) return http.HttpResponseNotAllowed(allowed_methods)
# parse_qs
try: try:
# python >= ? import markdown
from urlparse import parse_qs import re
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
"""Override markdown's SetextHeaderProcessor, so that ==== headers are <h2> and ---- headers are <h3>.
We use <h1> for the resource name."""
# Detect Setext-style header. Must be first 2 lines of block.
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
def test(self, parent, block):
return bool(self.RE.match(block))
def run(self, parent, blocks):
lines = blocks.pop(0).split('\n')
# Determine level. ``=`` is 1 and ``-`` is 2.
if lines[1].startswith('='):
level = 2
else:
level = 3
h = markdown.etree.SubElement(parent, 'h%d' % level)
h.text = lines[0].strip()
if len(lines) > 2:
# Block contains additional lines. Add to master blocks for later.
blocks.insert(0, '\n'.join(lines[2:]))
def apply_markdown(text):
"""Simple wrapper around markdown.markdown to apply our CustomSetextHeaderProcessor,
and also set the base level of '#' style headers to <h2>."""
extensions = ['headerid(level=2)']
safe_mode = False,
output_format = markdown.DEFAULT_OUTPUT_FORMAT
md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
safe_mode=safe_mode,
output_format=output_format)
md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
return md.convert(text)
except ImportError: except ImportError:
# python <= ? apply_markdown = None
from cgi import parse_qs

View File

@ -1,51 +0,0 @@
"""If python-markdown is installed expose an apply_markdown(text) function,
to convert markeddown text into html. Otherwise just set apply_markdown to None.
See: http://www.freewisdom.org/projects/python-markdown/
"""
__all__ = ['apply_markdown']
try:
import markdown
import re
class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
"""Override markdown's SetextHeaderProcessor, so that ==== headers are <h2> and ---- headers are <h3>.
We use <h1> for the resource name."""
# Detect Setext-style header. Must be first 2 lines of block.
RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
def test(self, parent, block):
return bool(self.RE.match(block))
def run(self, parent, blocks):
lines = blocks.pop(0).split('\n')
# Determine level. ``=`` is 1 and ``-`` is 2.
if lines[1].startswith('='):
level = 2
else:
level = 3
h = markdown.etree.SubElement(parent, 'h%d' % level)
h.text = lines[0].strip()
if len(lines) > 2:
# Block contains additional lines. Add to master blocks for later.
blocks.insert(0, '\n'.join(lines[2:]))
def apply_markdown(text):
"""Simple wrapper around markdown.markdown to apply our CustomSetextHeaderProcessor,
and also set the base level of '#' style headers to <h2>."""
extensions = ['headerid(level=2)']
safe_mode = False,
output_format = markdown.DEFAULT_OUTPUT_FORMAT
md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
safe_mode=safe_mode,
output_format=output_format)
md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
return md.convert(text)
except:
apply_markdown = None

View File

@ -1,4 +1,4 @@
from djangorestframework.mediatypes import MediaType from djangorestframework.utils.mediatypes import MediaType
from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework.parsers import FormParser, MultipartParser from djangorestframework.parsers import FormParser, MultipartParser
@ -397,7 +397,7 @@ class ResponseMixin(object):
class AuthMixin(object): class AuthMixin(object):
"""Mixin class to provide authentication and permission checking.""" """Mixin class to provide authentication and permission checking."""
authenticators = () authentication = ()
permissions = () permissions = ()
@property @property
@ -407,9 +407,9 @@ class AuthMixin(object):
return self._auth return self._auth
def _authenticate(self): def _authenticate(self):
for authenticator_cls in self.authenticators: for authentication_cls in self.authentication:
authenticator = authenticator_cls(self) authentication = authentication_cls(self)
auth = authenticator.authenticate(self.request) auth = authentication.authenticate(self.request)
if auth: if auth:
return auth return auth
return None return None

View File

@ -14,7 +14,7 @@ from django.utils import simplejson as json
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework import status from djangorestframework import status
from djangorestframework.utils import as_tuple from djangorestframework.utils import as_tuple
from djangorestframework.mediatypes import MediaType from djangorestframework.utils.mediatypes import MediaType
from djangorestframework.compat import parse_qs from djangorestframework.compat import parse_qs

View File

@ -1,7 +1,7 @@
"""Emitters are used to serialize a Resource's output into specific media types. """Renderers are used to serialize a Resource's output into specific media types.
django-rest-framework also provides HTML and PlainText emitters that help self-document the API, django-rest-framework also provides HTML and PlainText renderers that help self-document the API,
by serializing the output along with documentation regarding the Resource, output status and headers, by serializing the output along with documentation regarding the Resource, output status and headers,
and providing forms and links depending on the allowed methods, emitters and parsers on the Resource. and providing forms and links depending on the allowed methods, renderers and parsers on the Resource.
""" """
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -10,9 +10,9 @@ from django.utils import simplejson as json
from django import forms from django import forms
from djangorestframework.utils import dict2xml, url_resolves from djangorestframework.utils import dict2xml, url_resolves
from djangorestframework.markdownwrapper import apply_markdown from djangorestframework.compat import apply_markdown
from djangorestframework.breadcrumbs import get_breadcrumbs from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.description import get_name, get_description from djangorestframework.utils.description import get_name, get_description
from djangorestframework import status from djangorestframework import status
from urllib import quote_plus from urllib import quote_plus
@ -22,18 +22,18 @@ from decimal import Decimal
# TODO: Rename verbose to something more appropriate # TODO: Rename verbose to something more appropriate
# TODO: Maybe None could be handled more cleanly. It'd be nice if it was handled by default, # TODO: Maybe None could be handled more cleanly. It'd be nice if it was handled by default,
# and only have an emitter output anything if it explicitly provides support for that. # and only have an renderer output anything if it explicitly provides support for that.
class BaseEmitter(object): class BaseRenderer(object):
"""All emitters must extend this class, set the media_type attribute, and """All renderers must extend this class, set the media_type attribute, and
override the emit() function.""" override the render() function."""
media_type = None media_type = None
def __init__(self, resource): def __init__(self, resource):
self.resource = resource self.resource = resource
def emit(self, output=None, verbose=False): def render(self, output=None, verbose=False):
"""By default emit simply returns the ouput as-is. """By default render simply returns the ouput as-is.
Override this method to provide for other behaviour.""" Override this method to provide for other behaviour."""
if output is None: if output is None:
return '' return ''
@ -41,13 +41,13 @@ class BaseEmitter(object):
return output return output
class TemplateEmitter(BaseEmitter): class TemplateRenderer(BaseRenderer):
"""Provided for convienience. """Provided for convienience.
Emit the output by simply rendering it with the given template.""" Emit the output by simply rendering it with the given template."""
media_type = None media_type = None
template = None template = None
def emit(self, output=None, verbose=False): def render(self, output=None, verbose=False):
if output is None: if output is None:
return '' return ''
@ -55,23 +55,23 @@ class TemplateEmitter(BaseEmitter):
return self.template.render(context) return self.template.render(context)
class DocumentingTemplateEmitter(BaseEmitter): class DocumentingTemplateRenderer(BaseRenderer):
"""Base class for emitters used to self-document the API. """Base class for renderers used to self-document the API.
Implementing classes should extend this class and set the template attribute.""" Implementing classes should extend this class and set the template attribute."""
template = None template = None
def _get_content(self, resource, request, output): def _get_content(self, resource, request, output):
"""Get the content as if it had been emitted by a non-documenting emitter. """Get the content as if it had been renderted by a non-documenting renderer.
(Typically this will be the content as it would have been if the Resource had been (Typically this will be the content as it would have been if the Resource had been
requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)""" requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)"""
# Find the first valid emitter and emit the content. (Don't use another documenting emitter.) # Find the first valid renderer and render the content. (Don't use another documenting renderer.)
emitters = [emitter for emitter in resource.emitters if not isinstance(emitter, DocumentingTemplateEmitter)] renderers = [renderer for renderer in resource.renderers if not isinstance(renderer, DocumentingTemplateRenderer)]
if not emitters: if not renderers:
return '[No emitters were found]' return '[No renderers were found]'
content = emitters[0](resource).emit(output, verbose=True) content = renderers[0](resource).render(output, verbose=True)
if not all(char in string.printable for char in content): if not all(char in string.printable for char in content):
return '[%d bytes of binary content]' return '[%d bytes of binary content]'
@ -146,7 +146,7 @@ class DocumentingTemplateEmitter(BaseEmitter):
return GenericContentForm(resource) return GenericContentForm(resource)
def emit(self, output=None): def render(self, output=None):
content = self._get_content(self.resource, self.resource.request, output) content = self._get_content(self.resource, self.resource.request, output)
form_instance = self._get_form_instance(self.resource) form_instance = self._get_form_instance(self.resource)
@ -190,11 +190,11 @@ class DocumentingTemplateEmitter(BaseEmitter):
return ret return ret
class JSONEmitter(BaseEmitter): class JSONRenderer(BaseRenderer):
"""Emitter which serializes to JSON""" """Renderer which serializes to JSON"""
media_type = 'application/json' media_type = 'application/json'
def emit(self, output=None, verbose=False): def render(self, output=None, verbose=False):
if output is None: if output is None:
return '' return ''
if verbose: if verbose:
@ -202,42 +202,42 @@ class JSONEmitter(BaseEmitter):
return json.dumps(output) return json.dumps(output)
class XMLEmitter(BaseEmitter): class XMLRenderer(BaseRenderer):
"""Emitter which serializes to XML.""" """Renderer which serializes to XML."""
media_type = 'application/xml' media_type = 'application/xml'
def emit(self, output=None, verbose=False): def render(self, output=None, verbose=False):
if output is None: if output is None:
return '' return ''
return dict2xml(output) return dict2xml(output)
class DocumentingHTMLEmitter(DocumentingTemplateEmitter): class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
"""Emitter which provides a browsable HTML interface for an API. """Renderer which provides a browsable HTML interface for an API.
See the examples listed in the django-rest-framework documentation to see this in actions.""" See the examples listed in the django-rest-framework documentation to see this in actions."""
media_type = 'text/html' media_type = 'text/html'
template = 'emitter.html' template = 'renderer.html'
class DocumentingXHTMLEmitter(DocumentingTemplateEmitter): class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
"""Identical to DocumentingHTMLEmitter, except with an xhtml media type. """Identical to DocumentingHTMLRenderer, except with an xhtml media type.
We need this to be listed in preference to xml in order to return HTML to WebKit based browsers, We need this to be listed in preference to xml in order to return HTML to WebKit based browsers,
given their Accept headers.""" given their Accept headers."""
media_type = 'application/xhtml+xml' media_type = 'application/xhtml+xml'
template = 'emitter.html' template = 'renderer.html'
class DocumentingPlainTextEmitter(DocumentingTemplateEmitter): class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
"""Emitter that serializes the output with the default emitter, but also provides plain-text """Renderer that serializes the output with the default renderer, but also provides plain-text
doumentation of the returned status and headers, and of the resource's name and description. doumentation of the returned status and headers, and of the resource's name and description.
Useful for browsing an API with command line tools.""" Useful for browsing an API with command line tools."""
media_type = 'text/plain' media_type = 'text/plain'
template = 'emitter.txt' template = 'renderer.txt'
DEFAULT_EMITTERS = ( JSONEmitter, DEFAULT_RENDERERS = ( JSONRenderer,
DocumentingHTMLEmitter, DocumentingHTMLRenderer,
DocumentingXHTMLEmitter, DocumentingXHTMLRenderer,
DocumentingPlainTextEmitter, DocumentingPlainTextRenderer,
XMLEmitter ) XMLRenderer )

View File

@ -4,7 +4,7 @@ from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View from djangorestframework.compat import View
from djangorestframework.response import Response, ErrorResponse from djangorestframework.response import Response, ErrorResponse
from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin
from djangorestframework import renderers, parsers, authenticators, permissions, validators, status from djangorestframework import renderers, parsers, authentication, permissions, validators, status
# TODO: Figure how out references and named urls need to work nicely # TODO: Figure how out references and named urls need to work nicely
@ -37,8 +37,8 @@ class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
validators = ( validators.FormValidator, ) validators = ( validators.FormValidator, )
# List of all authenticating methods to attempt. # List of all authenticating methods to attempt.
authenticators = ( authenticators.UserLoggedInAuthenticator, authentication = ( authentication.UserLoggedInAuthenticator,
authenticators.BasicAuthenticator ) authentication.BasicAuthenticator )
# List of all permissions required to access the resource # List of all permissions required to access the resource
permissions = () permissions = ()

View File

@ -48,7 +48,7 @@
<h2>GET {{ name }}</h2> <h2>GET {{ name }}</h2>
<div class='submit-row' style='margin: 0; border: 0'> <div class='submit-row' style='margin: 0; border: 0'>
<a href='{{ request.path }}' rel="nofollow" style='float: left'>GET</a> <a href='{{ request.path }}' rel="nofollow" style='float: left'>GET</a>
{% for media_type in resource.emitted_media_types %} {% for media_type in resource.renderted_media_types %}
{% with resource.ACCEPT_QUERY_PARAM|add:"="|add:media_type as param %} {% with resource.ACCEPT_QUERY_PARAM|add:"="|add:media_type as param %}
[<a href='{{ request.path|add_query_param:param }}' rel="nofollow">{{ media_type }}</a>] [<a href='{{ request.path|add_query_param:param }}' rel="nofollow">{{ media_type }}</a>]
{% endwith %} {% endwith %}

View File

@ -1,6 +1,6 @@
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django.test import TestCase from django.test import TestCase
from djangorestframework.breadcrumbs import get_breadcrumbs from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.resource import Resource from djangorestframework.resource import Resource
class Root(Resource): class Root(Resource):

View File

@ -1,7 +1,7 @@
from django.test import TestCase from django.test import TestCase
from djangorestframework.resource import Resource from djangorestframework.resource import Resource
from djangorestframework.markdownwrapper import apply_markdown from djangorestframework.compat import apply_markdown
from djangorestframework.description import get_name, get_description from djangorestframework.utils.description import get_name, get_description
# We check that docstrings get nicely un-indented. # We check that docstrings get nicely un-indented.
DESCRIPTION = """an example docstring DESCRIPTION = """an example docstring

View File

@ -1,76 +0,0 @@
from django.conf.urls.defaults import patterns, url
from django import http
from django.test import TestCase
from djangorestframework.compat import View
from djangorestframework.emitters import BaseEmitter
from djangorestframework.mixins import ResponseMixin
from djangorestframework.response import Response
DUMMYSTATUS = 200
DUMMYCONTENT = 'dummycontent'
EMITTER_A_SERIALIZER = lambda x: 'Emitter A: %s' % x
EMITTER_B_SERIALIZER = lambda x: 'Emitter B: %s' % x
class MockView(ResponseMixin, View):
def get(self, request):
response = Response(DUMMYSTATUS, DUMMYCONTENT)
return self.emit(response)
class EmitterA(BaseEmitter):
media_type = 'mock/emittera'
def emit(self, output, verbose=False):
return EMITTER_A_SERIALIZER(output)
class EmitterB(BaseEmitter):
media_type = 'mock/emitterb'
def emit(self, output, verbose=False):
return EMITTER_B_SERIALIZER(output)
urlpatterns = patterns('',
url(r'^$', MockView.as_view(emitters=[EmitterA, EmitterB])),
)
class EmitterIntegrationTests(TestCase):
"""End-to-end testing of emitters using an EmitterMixin on a generic view."""
urls = 'djangorestframework.tests.emitters'
def test_default_emitter_serializes_content(self):
"""If the Accept header is not set the default emitter should serialize the response."""
resp = self.client.get('/')
self.assertEquals(resp['Content-Type'], EmitterA.media_type)
self.assertEquals(resp.content, EMITTER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_default_emitter_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default emitter should serialize the response."""
resp = self.client.get('/', HTTP_ACCEPT='*/*')
self.assertEquals(resp['Content-Type'], EmitterA.media_type)
self.assertEquals(resp.content, EMITTER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_emitter_serializes_content_default_case(self):
"""If the Accept header is set the specified emitter should serialize the response.
(In this case we check that works for the default emitter)"""
resp = self.client.get('/', HTTP_ACCEPT=EmitterA.media_type)
self.assertEquals(resp['Content-Type'], EmitterA.media_type)
self.assertEquals(resp.content, EMITTER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
def test_specified_emitter_serializes_content_non_default_case(self):
"""If the Accept header is set the specified emitter should serialize the response.
(In this case we check that works for a non-default emitter)"""
resp = self.client.get('/', HTTP_ACCEPT=EmitterB.media_type)
self.assertEquals(resp['Content-Type'], EmitterB.media_type)
self.assertEquals(resp.content, EMITTER_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, 406)

View File

@ -82,7 +82,7 @@ from django.test import TestCase
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.parsers import MultipartParser from djangorestframework.parsers import MultipartParser
from djangorestframework.resource import Resource from djangorestframework.resource import Resource
from djangorestframework.mediatypes import MediaType from djangorestframework.utils.mediatypes import MediaType
from StringIO import StringIO from StringIO import StringIO
def encode_multipart_formdata(fields, files): def encode_multipart_formdata(fields, files):

View File

@ -0,0 +1,76 @@
from django.conf.urls.defaults import patterns, url
from django import http
from django.test import TestCase
from djangorestframework.compat import View
from djangorestframework.renderers import BaseRenderer
from djangorestframework.mixins import ResponseMixin
from djangorestframework.response import Response
DUMMYSTATUS = 200
DUMMYCONTENT = 'dummycontent'
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
class MockView(ResponseMixin, View):
def get(self, request):
response = Response(DUMMYSTATUS, DUMMYCONTENT)
return self.render(response)
class RendererA(BaseRenderer):
media_type = 'mock/renderera'
def render(self, output, verbose=False):
return RENDERER_A_SERIALIZER(output)
class RendererB(BaseRenderer):
media_type = 'mock/rendererb'
def render(self, output, verbose=False):
return RENDERER_B_SERIALIZER(output)
urlpatterns = patterns('',
url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
)
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_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_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, 406)

View File

@ -1,13 +1,12 @@
import re
import xml.etree.ElementTree as ET
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.utils.xmlutils import SimplerXMLGenerator from django.utils.xmlutils import SimplerXMLGenerator
from django.core.urlresolvers import resolve from django.core.urlresolvers import resolve
from django.conf import settings from django.conf import settings
try:
import cStringIO as StringIO from djangorestframework.compat import StringIO
except ImportError:
import StringIO import re
import xml.etree.ElementTree as ET
#def admin_media_prefix(request): #def admin_media_prefix(request):

View File

@ -1,5 +1,5 @@
from django.core.urlresolvers import resolve from django.core.urlresolvers import resolve
from djangorestframework.description import get_name from djangorestframework.utils.description import get_name
def get_breadcrumbs(url): def get_breadcrumbs(url):
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url).""" """Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
@ -7,7 +7,6 @@ def get_breadcrumbs(url):
def breadcrumbs_recursive(url, breadcrumbs_list): def breadcrumbs_recursive(url, breadcrumbs_list):
"""Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url.""" """Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url."""
# This is just like compsci 101 all over again...
try: try:
(view, unused_args, unused_kwargs) = resolve(url) (view, unused_args, unused_kwargs) = resolve(url)
except: except: