Clean up bits of templates etc

This commit is contained in:
Tom Christie 2012-09-20 17:44:34 +01:00
parent f4670c8996
commit d9cba6398e
12 changed files with 100 additions and 158 deletions

View File

@ -5,10 +5,10 @@ Django REST framework also provides HTML and PlainText renderers that help self-
by serializing the output along with documentation regarding the View, output status and headers, by serializing the output along with documentation regarding the View, output status and headers,
and providing forms and links depending on the allowed methods, renderers and parsers on the View. and providing forms and links depending on the allowed methods, renderers and parsers on the View.
""" """
import string
from django import forms from django import forms
from django.template import RequestContext, loader from django.template import RequestContext, loader
from django.utils import simplejson as json from django.utils import simplejson as json
from rest_framework.compat import yaml from rest_framework.compat import yaml
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.utils import dict2xml from rest_framework.utils import dict2xml
@ -16,9 +16,7 @@ from rest_framework.utils import encoders
from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.utils.breadcrumbs import get_breadcrumbs
from rest_framework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches from rest_framework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
from rest_framework import VERSION from rest_framework import VERSION
from rest_framework.fields import FloatField, IntegerField, DateTimeField, DateField, EmailField, CharField, BooleanField from rest_framework import serializers
import string
class BaseRenderer(object): class BaseRenderer(object):
@ -27,8 +25,6 @@ class BaseRenderer(object):
and override the :meth:`render` method. and override the :meth:`render` method.
""" """
_FORMAT_QUERY_PARAM = 'format'
media_type = None media_type = None
format = None format = None
@ -72,7 +68,7 @@ class BaseRenderer(object):
class JSONRenderer(BaseRenderer): class JSONRenderer(BaseRenderer):
""" """
Renderer which serializes to JSON Renderer which serializes to json.
""" """
media_type = 'application/json' media_type = 'application/json'
@ -81,7 +77,7 @@ class JSONRenderer(BaseRenderer):
def render(self, obj=None, media_type=None): def render(self, obj=None, media_type=None):
""" """
Renders *obj* into serialized JSON. Render `obj` into json.
""" """
if obj is None: if obj is None:
return '' return ''
@ -96,28 +92,37 @@ class JSONRenderer(BaseRenderer):
except (ValueError, TypeError): except (ValueError, TypeError):
indent = None indent = None
return json.dumps(obj, cls=self.encoder_class, indent=indent, sort_keys=sort_keys) return json.dumps(obj, cls=self.encoder_class,
indent=indent, sort_keys=sort_keys)
class JSONPRenderer(JSONRenderer): class JSONPRenderer(JSONRenderer):
""" """
Renderer which serializes to JSONP Renderer which serializes to json,
wrapping the json output in a callback function.
""" """
media_type = 'application/javascript' media_type = 'application/javascript'
format = 'jsonp' format = 'jsonp'
renderer_class = JSONRenderer
callback_parameter = 'callback' callback_parameter = 'callback'
default_callback = 'callback'
def _get_callback(self): def get_callback(self):
return self.view.request.GET.get(self.callback_parameter, self.callback_parameter) """
Determine the name of the callback to wrap around the json output.
def _get_renderer(self): """
return self.renderer_class(self.view) params = self.view.request.GET
return params.get(self.callback_parameter, self.default_callback)
def render(self, obj=None, media_type=None): def render(self, obj=None, media_type=None):
callback = self._get_callback() """
json = self._get_renderer().render(obj, media_type) Renders into jsonp, wrapping the json output in a callback function.
Clients may set the callback function name using a query parameter
on the URL, for example: ?callback=exampleCallbackName
"""
callback = self.get_callback()
json = super(JSONPRenderer, self).render(obj, media_type)
return "%s(%s);" % (callback, json) return "%s(%s);" % (callback, json)
@ -180,13 +185,13 @@ class TemplateRenderer(BaseRenderer):
return template.render(context) return template.render(context)
class DocumentingTemplateRenderer(BaseRenderer): class DocumentingHTMLRenderer(BaseRenderer):
""" """
Base class for renderers used to self-document the API. HTML renderer used to self-document the API.
Implementing classes should extend this class and set the template attribute.
""" """
media_type = 'text/html'
template = None format = 'html'
template = 'rest_framework/api.html'
def _get_content(self, view, request, obj, media_type): def _get_content(self, view, request, obj, media_type):
""" """
@ -198,7 +203,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
# Find the first valid renderer and render the content. (Don't use another documenting renderer.) # Find the first valid renderer and render the content. (Don't use another documenting renderer.)
renderers = [renderer for renderer in view.renderer_classes renderers = [renderer for renderer in view.renderer_classes
if not issubclass(renderer, DocumentingTemplateRenderer)] if not issubclass(renderer, DocumentingHTMLRenderer)]
if not renderers: if not renderers:
return '[No renderers were found]' return '[No renderers were found]'
@ -219,13 +224,13 @@ class DocumentingTemplateRenderer(BaseRenderer):
return return
# We need to map our Fields to Django's Fields. # We need to map our Fields to Django's Fields.
field_mapping = dict([ field_mapping = dict([
[FloatField.__name__, forms.FloatField], [serializers.FloatField.__name__, forms.FloatField],
[IntegerField.__name__, forms.IntegerField], [serializers.IntegerField.__name__, forms.IntegerField],
[DateTimeField.__name__, forms.DateTimeField], [serializers.DateTimeField.__name__, forms.DateTimeField],
[DateField.__name__, forms.DateField], [serializers.DateField.__name__, forms.DateField],
[EmailField.__name__, forms.EmailField], [serializers.EmailField.__name__, forms.EmailField],
[CharField.__name__, forms.CharField], [serializers.CharField.__name__, forms.CharField],
[BooleanField.__name__, forms.BooleanField] [serializers.BooleanField.__name__, forms.BooleanField]
]) ])
# Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python # Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
@ -299,8 +304,11 @@ class DocumentingTemplateRenderer(BaseRenderer):
The context used in the template contains all the information The context used in the template contains all the information
needed to self-document the response to this request. needed to self-document the response to this request.
""" """
view = self.view
request = view.request
response = view.response
content = self._get_content(self.view, self.view.request, obj, media_type) content = self._get_content(view, request, obj, media_type)
put_form_instance = self._get_form_instance(self.view, 'put') put_form_instance = self._get_form_instance(self.view, 'put')
post_form_instance = self._get_form_instance(self.view, 'post') post_form_instance = self._get_form_instance(self.view, 'post')
@ -313,9 +321,9 @@ class DocumentingTemplateRenderer(BaseRenderer):
template = loader.get_template(self.template) template = loader.get_template(self.template)
context = RequestContext(self.view.request, { context = RequestContext(self.view.request, {
'content': content, 'content': content,
'view': self.view, 'view': view,
'request': self.view.request, 'request': request,
'response': self.view.response, 'response': response,
'description': description, 'description': description,
'name': name, 'name': name,
'version': VERSION, 'version': VERSION,
@ -324,8 +332,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
'available_formats': [renderer.format for renderer in self.view.renderer_classes], 'available_formats': [renderer.format for renderer in self.view.renderer_classes],
'put_form': put_form_instance, 'put_form': put_form_instance,
'post_form': post_form_instance, 'post_form': post_form_instance,
'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
'api_settings': api_settings 'api_settings': api_settings
}) })
@ -338,53 +344,3 @@ class DocumentingTemplateRenderer(BaseRenderer):
self.view.response.status_code = 200 self.view.response.status_code = 200
return ret return ret
class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
"""
Renderer which provides a browsable HTML interface for an API.
See the examples at http://api.django-rest-framework.org to see this in action.
"""
media_type = 'text/html'
format = 'html'
template = 'rest_framework/api.html'
class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
"""
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,
given their Accept headers.
"""
media_type = 'application/xhtml+xml'
format = 'xhtml'
template = 'rest_framework/api.html'
class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
"""
Renderer that serializes the object with the default renderer, but also provides plain-text
documentation of the returned status and headers, and of the resource's name and description.
Useful for browsing an API with command line tools.
"""
media_type = 'text/plain'
format = 'txt'
template = 'rest_framework/api.txt'
DEFAULT_RENDERERS = (
JSONRenderer,
JSONPRenderer,
DocumentingHTMLRenderer,
DocumentingXHTMLRenderer,
DocumentingPlainTextRenderer,
XMLRenderer
)
if yaml:
DEFAULT_RENDERERS += (YAMLRenderer, )
else:
YAMLRenderer = None

View File

@ -1,5 +1,5 @@
from django.template.response import SimpleTemplateResponse
from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.core.handlers.wsgi import STATUS_CODE_TEXT
from django.template.response import SimpleTemplateResponse
class Response(SimpleTemplateResponse): class Response(SimpleTemplateResponse):

View File

@ -1,9 +1,9 @@
from decimal import Decimal
from django.core.serializers.base import DeserializedObject
from django.utils.datastructures import SortedDict
import copy import copy
import datetime import datetime
import types import types
from decimal import Decimal
from django.core.serializers.base import DeserializedObject
from django.utils.datastructures import SortedDict
from rest_framework.fields import * from rest_framework.fields import *

View File

@ -24,9 +24,7 @@ from django.utils import importlib
DEFAULTS = { DEFAULTS = {
'DEFAULT_RENDERERS': ( 'DEFAULT_RENDERERS': (
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.JSONPRenderer',
'rest_framework.renderers.DocumentingHTMLRenderer', 'rest_framework.renderers.DocumentingHTMLRenderer',
'rest_framework.renderers.DocumentingPlainTextRenderer',
), ),
'DEFAULT_PARSERS': ( 'DEFAULT_PARSERS': (
'rest_framework.parsers.JSONParser', 'rest_framework.parsers.JSONParser',
@ -38,7 +36,8 @@ DEFAULTS = {
), ),
'DEFAULT_PERMISSIONS': (), 'DEFAULT_PERMISSIONS': (),
'DEFAULT_THROTTLES': (), 'DEFAULT_THROTTLES': (),
'DEFAULT_CONTENT_NEGOTIATION': 'rest_framework.negotiation.DefaultContentNegotiation', 'DEFAULT_CONTENT_NEGOTIATION':
'rest_framework.negotiation.DefaultContentNegotiation',
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None, 'UNAUTHENTICATED_TOKEN': None,

View File

@ -0,0 +1,5 @@
prettyPrint();
$('.js-tooltip').tooltip({
delay: 1000
});

View File

@ -1,8 +0,0 @@
{% autoescape off %}{{ name }}
{{ description }}
HTTP {{ response.status_code }} {{ response.status_text }}
{% for key, val in response.headers.items %}{{ key }}: {{ val }}
{% endfor %}
{{ content }}{% endautoescape %}

View File

@ -4,26 +4,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
{% block head %}
{% block meta %}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="robots" content="NONE,NOARCHIVE" />
{% block bootstrap_theme %}
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap-tweaks.css"/>
{% endblock %} {% endblock %}
<title>{% block title %}Django REST framework{% endblock %}</title>
{% block style %}
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap-tweaks.css"/>
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/prettify.css'/> <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/prettify.css'/>
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/style.css'/> <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/default.css'/>
{% block extrastyle %}{% endblock %} {% endblock %}
<title>{% block title %}Django REST framework - {{ name }}{% endblock %}</title>
{% block extrahead %}{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
{% endblock %}
</head> </head>
<body class="{% block bodyclass %}{% endblock %} container"> <body class="{% block bodyclass %}{% endblock %} container">
{% block navbar %}
<div class="navbar navbar-fixed-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"> <div class="navbar navbar-fixed-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <div class="container">
@ -32,31 +34,28 @@
</span> </span>
<ul class="nav pull-right"> <ul class="nav pull-right">
{% block userlinks %} {% block userlinks %}
{% if user.is_active %} {% if user.is_authenticated %}
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">
Welcome, {{ user }} Welcome, {{ user }}
<b class="caret"></b> <b class="caret"></b>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li>{% optional_logout %}</li> <li>{% optional_logout request %}</li>
</ul> </ul>
</li> </li>
{% else %} {% else %}
<li>{% optional_login %}</li> <li>{% optional_login request %}</li>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
{% block global_heading %}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
<ul class="breadcrumb"> <ul class="breadcrumb">
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %} {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
<li> <li>
<a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>{{ breadcrumb_name }}</a> {% if not forloop.last %}<span class="divider">&rsaquo;</span>{% endif %} <a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>{{ breadcrumb_name }}</a> {% if not forloop.last %}<span class="divider">&rsaquo;</span>{% endif %}
@ -79,11 +78,9 @@
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for format in available_formats %} {% for format in available_formats %}
{% with FORMAT_PARAM|add:"="|add:format as param %} <li>
<li> <a class="js-tooltip format-option" href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}' rel="nofollow" title="Do a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
<a class="js-tooltip format-option" href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow" title="Do a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a> </li>
</li>
{% endwith %}
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -188,24 +185,19 @@
</div> </div>
<!-- END Content --> <!-- END Content -->
{% block footer %}
<div id="footer"> <div id="footer">
{% block footer %} <a class="powered-by" href='http://django-rest-framework.org'>Django REST framework</a> <span class="version">{{ version }}</span>
<a class="powered-by" href='http://django-rest-framework.org'>Django REST framework</a> <span class="version">{{ version }}</span>
{% endblock %}
</div> </div>
{% endblock %}
</div> </div>
{% block script %}
<script src="{% get_static_prefix %}rest_framework/js/jquery-1.8.1-min.js"></script> <script src="{% get_static_prefix %}rest_framework/js/jquery-1.8.1-min.js"></script>
<script src="{% get_static_prefix %}rest_framework/js/bootstrap.min.js"></script> <script src="{% get_static_prefix %}rest_framework/js/bootstrap.min.js"></script>
<script src="{% get_static_prefix %}rest_framework/js/prettify-min.js"></script> <script src="{% get_static_prefix %}rest_framework/js/prettify-min.js"></script>
<script> <script src="{% get_static_prefix %}rest_framework/js/default.js"></script>
prettyPrint(); {% endblock %}
$('.js-tooltip').tooltip({
delay: 1000
});
</script>
{% block extrabody %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -46,8 +46,8 @@ def replace_query_param(url, key, val):
# And the template tags themselves... # And the template tags themselves...
@register.simple_tag(takes_context=True) @register.simple_tag
def optional_login(context): def optional_login(request):
""" """
Include a login snippet if REST framework's login view is in the URLconf. Include a login snippet if REST framework's login view is in the URLconf.
""" """
@ -56,13 +56,12 @@ def optional_login(context):
except NoReverseMatch: except NoReverseMatch:
return '' return ''
request = context['request']
snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path) snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path)
return snippet return snippet
@register.simple_tag(takes_context=True) @register.simple_tag
def optional_logout(context): def optional_logout(request):
""" """
Include a logout snippet if REST framework's logout view is in the URLconf. Include a logout snippet if REST framework's logout view is in the URLconf.
""" """
@ -71,17 +70,16 @@ def optional_logout(context):
except NoReverseMatch: except NoReverseMatch:
return '' return ''
request = context['request']
snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path) snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
return snippet return snippet
@register.filter @register.simple_tag
def add_query_param(url, param): def add_query_param(request, key, val):
""" """
Add a query parameter to the current request url, and return the new url.
""" """
key, val = param.split('=') return replace_query_param(request.get_full_path(), key, val)
return replace_query_param(url, key, val)
@register.filter @register.filter
@ -114,7 +112,7 @@ def add_class(value, css_class):
return value return value
@register.filter(is_safe=True) @register.filter
def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True): def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):
""" """
Converts any URLs in text into clickable links. Converts any URLs in text into clickable links.
@ -170,4 +168,4 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
words[i] = mark_safe(word) words[i] = mark_safe(word)
elif autoescape: elif autoescape:
words[i] = escape(word) words[i] = escape(word)
return u''.join(words) return mark_safe(u''.join(words))

View File

@ -4,6 +4,7 @@ from django.conf.urls.defaults import patterns, url, include
from django.test import TestCase from django.test import TestCase
from rest_framework import status from rest_framework import status
from rest_framework.compat import yaml
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
@ -246,7 +247,7 @@ class JSONPRendererTests(TestCase):
self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr)) self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr))
if YAMLRenderer: if yaml:
_yaml_repr = 'foo: [bar, baz]\n' _yaml_repr = 'foo: [bar, baz]\n'
class YAMLRendererTests(TestCase): class YAMLRendererTests(TestCase):

View File

@ -1,6 +1,6 @@
import time
from django.core.cache import cache from django.core.cache import cache
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
import time
class BaseThrottle(object): class BaseThrottle(object):

View File

@ -11,12 +11,11 @@ from django.http import Http404
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
from rest_framework.compat import View as _View, apply_markdown from rest_framework.compat import View, apply_markdown
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework import status, exceptions
def _remove_trailing_string(content, trailing): def _remove_trailing_string(content, trailing):
@ -53,7 +52,7 @@ def _camelcase_to_spaces(content):
return re.sub(camelcase_boundry, ' \\1', content).strip() return re.sub(camelcase_boundry, ' \\1', content).strip()
class APIView(_View): class APIView(View):
settings = api_settings settings = api_settings
renderer_classes = api_settings.DEFAULT_RENDERERS renderer_classes = api_settings.DEFAULT_RENDERERS
@ -86,7 +85,7 @@ class APIView(_View):
def default_response_headers(self): def default_response_headers(self):
return { return {
'Allow': ', '.join(self.allowed_methods), 'Allow': ', '.join(self.allowed_methods),
'Vary': 'Authenticate, Accept' 'Vary': 'Accept'
} }
def get_name(self): def get_name(self):