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,
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
"""
import string
from django import forms
from django.template import RequestContext, loader
from django.utils import simplejson as json
from rest_framework.compat import yaml
from rest_framework.settings import api_settings
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.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
from rest_framework import VERSION
from rest_framework.fields import FloatField, IntegerField, DateTimeField, DateField, EmailField, CharField, BooleanField
import string
from rest_framework import serializers
class BaseRenderer(object):
@ -27,8 +25,6 @@ class BaseRenderer(object):
and override the :meth:`render` method.
"""
_FORMAT_QUERY_PARAM = 'format'
media_type = None
format = None
@ -72,7 +68,7 @@ class BaseRenderer(object):
class JSONRenderer(BaseRenderer):
"""
Renderer which serializes to JSON
Renderer which serializes to json.
"""
media_type = 'application/json'
@ -81,7 +77,7 @@ class JSONRenderer(BaseRenderer):
def render(self, obj=None, media_type=None):
"""
Renders *obj* into serialized JSON.
Render `obj` into json.
"""
if obj is None:
return ''
@ -96,28 +92,37 @@ class JSONRenderer(BaseRenderer):
except (ValueError, TypeError):
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):
"""
Renderer which serializes to JSONP
Renderer which serializes to json,
wrapping the json output in a callback function.
"""
media_type = 'application/javascript'
format = 'jsonp'
renderer_class = JSONRenderer
callback_parameter = 'callback'
default_callback = 'callback'
def _get_callback(self):
return self.view.request.GET.get(self.callback_parameter, self.callback_parameter)
def _get_renderer(self):
return self.renderer_class(self.view)
def get_callback(self):
"""
Determine the name of the callback to wrap around the json output.
"""
params = self.view.request.GET
return params.get(self.callback_parameter, self.default_callback)
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)
@ -180,13 +185,13 @@ class TemplateRenderer(BaseRenderer):
return template.render(context)
class DocumentingTemplateRenderer(BaseRenderer):
class DocumentingHTMLRenderer(BaseRenderer):
"""
Base class for renderers used to self-document the API.
Implementing classes should extend this class and set the template attribute.
HTML renderer used to self-document the API.
"""
template = None
media_type = 'text/html'
format = 'html'
template = 'rest_framework/api.html'
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.)
renderers = [renderer for renderer in view.renderer_classes
if not issubclass(renderer, DocumentingTemplateRenderer)]
if not issubclass(renderer, DocumentingHTMLRenderer)]
if not renderers:
return '[No renderers were found]'
@ -219,13 +224,13 @@ class DocumentingTemplateRenderer(BaseRenderer):
return
# We need to map our Fields to Django's Fields.
field_mapping = dict([
[FloatField.__name__, forms.FloatField],
[IntegerField.__name__, forms.IntegerField],
[DateTimeField.__name__, forms.DateTimeField],
[DateField.__name__, forms.DateField],
[EmailField.__name__, forms.EmailField],
[CharField.__name__, forms.CharField],
[BooleanField.__name__, forms.BooleanField]
[serializers.FloatField.__name__, forms.FloatField],
[serializers.IntegerField.__name__, forms.IntegerField],
[serializers.DateTimeField.__name__, forms.DateTimeField],
[serializers.DateField.__name__, forms.DateField],
[serializers.EmailField.__name__, forms.EmailField],
[serializers.CharField.__name__, forms.CharField],
[serializers.BooleanField.__name__, forms.BooleanField]
])
# 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
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')
post_form_instance = self._get_form_instance(self.view, 'post')
@ -313,9 +321,9 @@ class DocumentingTemplateRenderer(BaseRenderer):
template = loader.get_template(self.template)
context = RequestContext(self.view.request, {
'content': content,
'view': self.view,
'request': self.view.request,
'response': self.view.response,
'view': view,
'request': request,
'response': response,
'description': description,
'name': name,
'version': VERSION,
@ -324,8 +332,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
'available_formats': [renderer.format for renderer in self.view.renderer_classes],
'put_form': put_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
})
@ -338,53 +344,3 @@ class DocumentingTemplateRenderer(BaseRenderer):
self.view.response.status_code = 200
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.template.response import 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 datetime
import types
from decimal import Decimal
from django.core.serializers.base import DeserializedObject
from django.utils.datastructures import SortedDict
from rest_framework.fields import *

View File

@ -24,9 +24,7 @@ from django.utils import importlib
DEFAULTS = {
'DEFAULT_RENDERERS': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.JSONPRenderer',
'rest_framework.renderers.DocumentingHTMLRenderer',
'rest_framework.renderers.DocumentingPlainTextRenderer',
),
'DEFAULT_PARSERS': (
'rest_framework.parsers.JSONParser',
@ -38,7 +36,8 @@ DEFAULTS = {
),
'DEFAULT_PERMISSIONS': (),
'DEFAULT_THROTTLES': (),
'DEFAULT_CONTENT_NEGOTIATION': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_CONTENT_NEGOTIATION':
'rest_framework.negotiation.DefaultContentNegotiation',
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'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>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
{% block head %}
{% block bootstrap_theme %}
{% block meta %}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="robots" content="NONE,NOARCHIVE" />
{% 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"/>
{% endblock %}
<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'/>
{% block extrastyle %}{% endblock %}
<title>{% block title %}Django REST framework - {{ name }}{% endblock %}</title>
{% block extrahead %}{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/default.css'/>
{% endblock %}
{% endblock %}
</head>
<body class="{% block bodyclass %}{% endblock %} container">
{% block navbar %}
<div class="navbar navbar-fixed-top {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
<div class="navbar-inner">
<div class="container">
@ -32,31 +34,28 @@
</span>
<ul class="nav pull-right">
{% block userlinks %}
{% if user.is_active %}
{% if user.is_authenticated %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Welcome, {{ user }}
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>{% optional_logout %}</li>
<li>{% optional_logout request %}</li>
</ul>
</li>
{% else %}
<li>{% optional_login %}</li>
<li>{% optional_login request %}</li>
{% endif %}
{% endblock %}
</ul>
</div>
</div>
</div>
{% block global_heading %}{% endblock %}
{% endblock %}
{% block breadcrumbs %}
<ul class="breadcrumb">
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
<li>
<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>
<ul class="dropdown-menu">
{% for format in available_formats %}
{% with FORMAT_PARAM|add:"="|add:format as param %}
<li>
<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>
<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>
</li>
{% endwith %}
{% endfor %}
</ul>
</div>
@ -188,24 +185,19 @@
</div>
<!-- END Content -->
<div id="footer">
{% block footer %}
<div id="footer">
<a class="powered-by" href='http://django-rest-framework.org'>Django REST framework</a> <span class="version">{{ version }}</span>
</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/bootstrap.min.js"></script>
<script src="{% get_static_prefix %}rest_framework/js/prettify-min.js"></script>
<script>
prettyPrint();
$('.js-tooltip').tooltip({
delay: 1000
});
</script>
{% block extrabody %}{% endblock %}
<script src="{% get_static_prefix %}rest_framework/js/default.js"></script>
{% endblock %}
</body>
</html>

View File

@ -46,8 +46,8 @@ def replace_query_param(url, key, val):
# And the template tags themselves...
@register.simple_tag(takes_context=True)
def optional_login(context):
@register.simple_tag
def optional_login(request):
"""
Include a login snippet if REST framework's login view is in the URLconf.
"""
@ -56,13 +56,12 @@ def optional_login(context):
except NoReverseMatch:
return ''
request = context['request']
snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path)
return snippet
@register.simple_tag(takes_context=True)
def optional_logout(context):
@register.simple_tag
def optional_logout(request):
"""
Include a logout snippet if REST framework's logout view is in the URLconf.
"""
@ -71,17 +70,16 @@ def optional_logout(context):
except NoReverseMatch:
return ''
request = context['request']
snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
return snippet
@register.filter
def add_query_param(url, param):
@register.simple_tag
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(url, key, val)
return replace_query_param(request.get_full_path(), key, val)
@register.filter
@ -114,7 +112,7 @@ def add_class(value, css_class):
return value
@register.filter(is_safe=True)
@register.filter
def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):
"""
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)
elif autoescape:
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 rest_framework import status
from rest_framework.compat import yaml
from rest_framework.response import Response
from rest_framework.views import APIView
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))
if YAMLRenderer:
if yaml:
_yaml_repr = 'foo: [bar, baz]\n'
class YAMLRendererTests(TestCase):

View File

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

View File

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