mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Merge branch 'restframework2' of git://github.com/tomchristie/django-rest-framework into improved-view-decorators
* 'restframework2' of git://github.com/tomchristie/django-rest-framework: (56 commits) Bits of cleanup Add request.QUERY_PARAMS Add readonly 'id' field Tweak browseable API Don't display readonly fields Fix some bits of serialization Add csrf note Fix incorrect bit of tutorial Added tox.ini Tweak media_type -> accepted_media_type. Need to document, but marginally less confusing Tweak media_type -> accepted_media_type. Need to document, but marginally less confusing Tweak media_type -> accepted_media_type. Need to document, but marginally less confusing Clean up bits of templates etc Hack out bunch of unneccesary private methods on View class Clean up template tags Remove dumbass __all__ variables Remove old 'djangorestframework directories Change package name: djangorestframework -> rest_framework Dont strip final '/' Use get_script_prefix to play nicely if not installed at the root. ... Conflicts: rest_framework/decorators.py
This commit is contained in:
commit
01770c53cd
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,7 +7,7 @@ html/
|
|||
coverage/
|
||||
build/
|
||||
dist/
|
||||
djangorestframework.egg-info/
|
||||
rest_framework.egg-info/
|
||||
MANIFEST
|
||||
|
||||
!.gitignore
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
recursive-include djangorestframework/static *.ico *.txt *.css
|
||||
recursive-include djangorestframework/templates *.txt *.html
|
||||
recursive-include examples .keep *.py *.txt
|
||||
recursive-include docs *.py *.rst *.html *.txt
|
||||
include AUTHORS LICENSE CHANGELOG.rst requirements.txt tox.ini
|
||||
recursive-include rest_framework/static *.js *.css *.png
|
||||
recursive-include rest_framework/templates *.txt *.html
|
||||
|
|
|
@ -16,7 +16,6 @@ For more information, check out [the documentation][docs], in particular, the tu
|
|||
|
||||
* Python (2.6, 2.7)
|
||||
* Django (1.3, 1.4, 1.5)
|
||||
* [URLObject][urlobject] (2.0.0+)
|
||||
|
||||
**Optional:**
|
||||
|
||||
|
@ -29,7 +28,7 @@ For more information, check out [the documentation][docs], in particular, the tu
|
|||
|
||||
Install using `pip`...
|
||||
|
||||
pip install djangorestframework
|
||||
pip install rest_framework
|
||||
|
||||
...or clone the project from github.
|
||||
|
||||
|
@ -48,7 +47,7 @@ To build the docs.
|
|||
|
||||
To run the tests.
|
||||
|
||||
./djangorestframework/runtests/runtests.py
|
||||
./rest_framework/runtests/runtests.py
|
||||
|
||||
# Changelog
|
||||
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
"""
|
||||
The :mod:`response` module provides :class:`Response` and :class:`ImmediateResponse` classes.
|
||||
|
||||
`Response` is a subclass of `HttpResponse`, and can be similarly instantiated and returned
|
||||
from any view. It is a bit smarter than Django's `HttpResponse`, for it renders automatically
|
||||
its content to a serial format by using a list of :mod:`renderers`.
|
||||
|
||||
To determine the content type to which it must render, default behaviour is to use standard
|
||||
HTTP Accept header content negotiation. But `Response` also supports overriding the content type
|
||||
by specifying an ``_accept=`` parameter in the URL. Also, `Response` will ignore `Accept` headers
|
||||
from Internet Explorer user agents and use a sensible browser `Accept` header instead.
|
||||
"""
|
||||
|
||||
|
||||
import re
|
||||
from django.template.response import SimpleTemplateResponse
|
||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework.utils.mediatypes import order_by_precedence
|
||||
from djangorestframework import status
|
||||
|
||||
|
||||
MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
|
||||
|
||||
|
||||
class NotAcceptable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Response(SimpleTemplateResponse):
|
||||
"""
|
||||
An HttpResponse that may include content that hasn't yet been serialized.
|
||||
|
||||
Kwargs:
|
||||
- content(object). The raw content, not yet serialized.
|
||||
This must be native Python data that renderers can handle.
|
||||
(e.g.: `dict`, `str`, ...)
|
||||
- renderer_classes(list/tuple). The renderers to use for rendering the response content.
|
||||
"""
|
||||
|
||||
_ACCEPT_QUERY_PARAM = api_settings.URL_ACCEPT_OVERRIDE
|
||||
_IGNORE_IE_ACCEPT_HEADER = True
|
||||
|
||||
def __init__(self, content=None, status=None, headers=None, view=None,
|
||||
request=None, renderer_classes=None, format=None):
|
||||
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
|
||||
# which we don't need
|
||||
super(Response, self).__init__(None, status=status)
|
||||
|
||||
self.raw_content = content
|
||||
self.has_content_body = content is not None
|
||||
self.headers = headers and headers[:] or []
|
||||
self.view = view
|
||||
self.request = request
|
||||
self.renderer_classes = renderer_classes
|
||||
self.format = format
|
||||
|
||||
def get_renderers(self):
|
||||
"""
|
||||
Instantiates and returns the list of renderers the response will use.
|
||||
"""
|
||||
if self.renderer_classes is None:
|
||||
renderer_classes = api_settings.DEFAULT_RENDERERS
|
||||
else:
|
||||
renderer_classes = self.renderer_classes
|
||||
|
||||
if self.format:
|
||||
return [cls(self.view) for cls in renderer_classes
|
||||
if cls.format == self.format]
|
||||
return [cls(self.view) for cls in renderer_classes]
|
||||
|
||||
@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.
|
||||
"""
|
||||
renderer, media_type = self._determine_renderer()
|
||||
|
||||
# Set the media type of the response
|
||||
self['Content-Type'] = renderer.media_type
|
||||
|
||||
# Render the response content
|
||||
if self.has_content_body:
|
||||
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):
|
||||
"""
|
||||
Returns reason text corresponding to our HTTP response status code.
|
||||
Provided for convenience.
|
||||
"""
|
||||
return STATUS_CODE_TEXT.get(self.status_code, '')
|
||||
|
||||
def _determine_accept_list(self):
|
||||
"""
|
||||
Returns a list of accepted media types. This list is determined from :
|
||||
|
||||
1. overload with `_ACCEPT_QUERY_PARAM`
|
||||
2. `Accept` header of the request
|
||||
|
||||
If those are useless, a default value is returned instead.
|
||||
"""
|
||||
request = self.request
|
||||
|
||||
if (self._ACCEPT_QUERY_PARAM and
|
||||
request.GET.get(self._ACCEPT_QUERY_PARAM, None)):
|
||||
# Use _accept parameter override
|
||||
return [request.GET.get(self._ACCEPT_QUERY_PARAM)]
|
||||
elif (self._IGNORE_IE_ACCEPT_HEADER and
|
||||
'HTTP_USER_AGENT' in request.META and
|
||||
MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT']) and
|
||||
request.META.get('HTTP_X_REQUESTED_WITH', '') != 'XMLHttpRequest'):
|
||||
# Ignore MSIE's broken accept behavior except for AJAX requests
|
||||
# and do something sensible instead
|
||||
return ['text/html', '*/*']
|
||||
elif 'HTTP_ACCEPT' in request.META:
|
||||
# Use standard HTTP Accept negotiation
|
||||
return [token.strip() for token in request.META['HTTP_ACCEPT'].split(',')]
|
||||
else:
|
||||
# No accept header specified
|
||||
return ['*/*']
|
||||
|
||||
def _determine_renderer(self):
|
||||
"""
|
||||
Determines the appropriate renderer for the output, given the list of
|
||||
accepted media types, and the :attr:`renderer_classes` 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
|
||||
"""
|
||||
|
||||
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_set in order_by_precedence(accepts):
|
||||
for renderer in renderers:
|
||||
for media_type in media_type_set:
|
||||
if renderer.can_handle_response(media_type):
|
||||
return renderer, media_type
|
||||
|
||||
# No acceptable renderers were found
|
||||
raise NotAcceptable
|
||||
|
||||
def _get_406_response(self):
|
||||
renderer = self.renderer_classes[0]
|
||||
return Response(
|
||||
{
|
||||
'detail': 'Could not satisfy the client\'s Accept header',
|
||||
'available_types': [renderer.media_type
|
||||
for renderer in self.renderer_classes]
|
||||
},
|
||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
view=self.view, request=self.request, renderer_classes=[renderer])
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +0,0 @@
|
|||
{% autoescape off %}{{ name }}
|
||||
|
||||
{{ description }}
|
||||
|
||||
HTTP {{ response.status }} {{ response.status_text }}
|
||||
{% for key, val in response.headers.items %}{{ key }}: {{ val }}
|
||||
{% endfor %}
|
||||
{{ content }}{% endautoescape %}
|
|
@ -1,151 +0,0 @@
|
|||
{% load url from future %}
|
||||
{% load urlize_quoted_links %}
|
||||
{% load add_query_param %}
|
||||
{% load optional_login %}
|
||||
{% load static %}
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/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 %}
|
||||
</head>
|
||||
<body class="{% block bodyclass %}{% endblock %}">
|
||||
<div id="container">
|
||||
|
||||
<div id="header">
|
||||
<div id="branding">
|
||||
<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">
|
||||
{% block userlinks %}
|
||||
{% if user.is_active %}
|
||||
Welcome, {{ user }}.
|
||||
{% optional_login %}
|
||||
{% else %}
|
||||
{% optional_logout %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block nav-global %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="breadcrumbs">
|
||||
{% block breadcrumbs %}
|
||||
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
||||
<a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}›{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div id="content" class="{% block coltype %}colM{% endblock %}">
|
||||
|
||||
{% if 'OPTIONS' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form action="{{ request.get_full_path }}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
|
||||
<input type="submit" value="OPTIONS" class="default" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class='content-main'>
|
||||
<h1>{{ name }}</h1>
|
||||
<p>{{ description }}</p>
|
||||
<div class='module'>
|
||||
<pre><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
|
||||
{% for key, val in response.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
|
||||
{% endfor %}
|
||||
{{ content|urlize_quoted_links }}</pre>{% endautoescape %}</div>
|
||||
|
||||
{% if 'GET' in allowed_methods %}
|
||||
<form>
|
||||
<fieldset class='module aligned'>
|
||||
<h2>GET {{ name }}</h2>
|
||||
<div class='submit-row' style='margin: 0; border: 0'>
|
||||
<a href='{{ request.get_full_path }}' rel="nofollow" style='float: left'>GET</a>
|
||||
{% for format in available_formats %}
|
||||
{% with FORMAT_PARAM|add:"="|add:format as param %}
|
||||
[<a href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow">{{ format }}</a>]
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled and the user has permissions on this view. #}
|
||||
{% if response.status_code != 403 %}
|
||||
|
||||
{% if 'POST' in allowed_methods %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
|
||||
<fieldset class='module aligned'>
|
||||
<h2>POST {{ name }}</h2>
|
||||
{% csrf_token %}
|
||||
{{ post_form.non_field_errors }}
|
||||
{% for field in post_form %}
|
||||
<div class='form-row'>
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
<span class='help'>{{ field.help_text }}</span>
|
||||
{{ field.errors }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class='submit-row' style='margin: 0; border: 0'>
|
||||
<input type="submit" value="POST" class="default" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'PUT' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
|
||||
<fieldset class='module aligned'>
|
||||
<h2>PUT {{ name }}</h2>
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" />
|
||||
{% csrf_token %}
|
||||
{{ put_form.non_field_errors }}
|
||||
{% for field in put_form %}
|
||||
<div class='form-row'>
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
<span class='help'>{{ field.help_text }}</span>
|
||||
{{ field.errors }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class='submit-row' style='margin: 0; border: 0'>
|
||||
<input type="submit" value="PUT" class="default" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'DELETE' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form action="{{ request.get_full_path }}" method="POST">
|
||||
<fieldset class='module aligned'>
|
||||
<h2>DELETE {{ name }}</h2>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
|
||||
<div class='submit-row' style='margin: 0; border: 0'>
|
||||
<input type="submit" value="DELETE" class="default" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- END content-main -->
|
||||
|
||||
</div>
|
||||
<!-- END Content -->
|
||||
|
||||
{% block footer %}<div id="footer"></div>{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,10 +0,0 @@
|
|||
from django.template import Library
|
||||
from urlobject import URLObject
|
||||
register = Library()
|
||||
|
||||
|
||||
def add_query_param(url, param):
|
||||
return unicode(URLObject(url).with_query(param))
|
||||
|
||||
|
||||
register.filter('add_query_param', add_query_param)
|
|
@ -1,32 +0,0 @@
|
|||
"""
|
||||
Tags to optionally include the login and logout links, depending on if the
|
||||
login and logout views are in the urlconf.
|
||||
"""
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def optional_login(context):
|
||||
try:
|
||||
login_url = reverse('djangorestframework:login')
|
||||
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):
|
||||
try:
|
||||
logout_url = reverse('djangorestframework:logout')
|
||||
except NoReverseMatch:
|
||||
return ''
|
||||
|
||||
request = context['request']
|
||||
snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
|
||||
return snippet
|
|
@ -1,83 +0,0 @@
|
|||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.response import Response
|
||||
|
||||
|
||||
# See: http://www.useragentstring.com/
|
||||
MSIE_9_USER_AGENT = 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'
|
||||
MSIE_8_USER_AGENT = 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)'
|
||||
MSIE_7_USER_AGENT = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)'
|
||||
FIREFOX_4_0_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)'
|
||||
CHROME_11_0_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17'
|
||||
SAFARI_5_0_USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-ca) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/531.2+'
|
||||
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
|
||||
"""
|
||||
|
||||
urls = 'djangorestframework.tests.accept'
|
||||
|
||||
def setUp(self):
|
||||
|
||||
class MockView(APIView):
|
||||
permissions = ()
|
||||
response_class = Response
|
||||
|
||||
def get(self, request):
|
||||
return self.response_class({'a': 1, 'b': 2, 'c': 3})
|
||||
|
||||
self.req = RequestFactory()
|
||||
self.MockView = MockView
|
||||
self.view = MockView.as_view()
|
||||
|
||||
def test_munge_msie_accept_header(self):
|
||||
"""Send MSIE user agent strings and ensure that we get an HTML response,
|
||||
even if we set a */* accept header."""
|
||||
for user_agent in (MSIE_9_USER_AGENT,
|
||||
MSIE_8_USER_AGENT,
|
||||
MSIE_7_USER_AGENT):
|
||||
req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
|
||||
resp = self.view(req)
|
||||
resp.render()
|
||||
self.assertEqual(resp['Content-Type'], 'text/html')
|
||||
|
||||
def test_dont_rewrite_msie_accept_header(self):
|
||||
"""Turn off _IGNORE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure
|
||||
that we get a JSON response if we set a */* accept header."""
|
||||
class IgnoreIEAcceptResponse(Response):
|
||||
_IGNORE_IE_ACCEPT_HEADER = False
|
||||
view = self.MockView.as_view(response_class=IgnoreIEAcceptResponse)
|
||||
|
||||
for user_agent in (MSIE_9_USER_AGENT,
|
||||
MSIE_8_USER_AGENT,
|
||||
MSIE_7_USER_AGENT):
|
||||
req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
|
||||
resp = view(req)
|
||||
resp.render()
|
||||
self.assertEqual(resp['Content-Type'], 'application/json')
|
||||
|
||||
def test_dont_munge_nice_browsers_accept_header(self):
|
||||
"""Send Non-MSIE user agent strings and ensure that we get a JSON response,
|
||||
if we set a */* Accept header. (Other browsers will correctly set the Accept header)"""
|
||||
for user_agent in (FIREFOX_4_0_USER_AGENT,
|
||||
CHROME_11_0_USER_AGENT,
|
||||
SAFARI_5_0_USER_AGENT,
|
||||
OPERA_11_0_MSIE_USER_AGENT,
|
||||
OPERA_11_0_OPERA_USER_AGENT):
|
||||
req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
|
||||
resp = self.view(req)
|
||||
resp.render()
|
||||
self.assertEqual(resp['Content-Type'], 'application/json')
|
|
@ -28,10 +28,10 @@ The value of `request.user` and `request.auth` for unauthenticated requests can
|
|||
|
||||
The default authentication policy may be set globally, using the `DEFAULT_AUTHENTICATION` setting. For example.
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION': (
|
||||
'djangorestframework.authentication.UserBasicAuthentication',
|
||||
'djangorestframework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.UserBasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -75,11 +75,11 @@ If successfully authenticated, `BasicAuthentication` provides the following cred
|
|||
|
||||
This policy uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.
|
||||
|
||||
To use the `TokenAuthentication` policy, include `djangorestframework.authtoken` in your `INSTALLED_APPS` setting.
|
||||
To use the `TokenAuthentication` policy, include `rest_framework.authtoken` in your `INSTALLED_APPS` setting.
|
||||
|
||||
You'll also need to create tokens for your users.
|
||||
|
||||
from djangorestframework.authtoken.models import Token
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
token = Token.objects.create(user=...)
|
||||
print token.key
|
||||
|
@ -91,7 +91,7 @@ For clients to authenticate, the token key should be included in the `Authorizat
|
|||
If successfully authenticated, `TokenAuthentication` provides the following credentials.
|
||||
|
||||
* `request.user` will be a `django.contrib.auth.models.User` instance.
|
||||
* `request.auth` will be a `djangorestframework.tokenauth.models.BasicToken` instance.
|
||||
* `request.auth` will be a `rest_framework.tokenauth.models.BasicToken` instance.
|
||||
|
||||
**Note:** If you use `TokenAuthentication` in production you must ensure that your API is only available over `https` only.
|
||||
|
||||
|
@ -102,7 +102,7 @@ This policy uses the [OAuth 2.0][oauth] protocol to authenticate requests. OAut
|
|||
If successfully authenticated, `OAuthAuthentication` provides the following credentials.
|
||||
|
||||
* `request.user` will be a `django.contrib.auth.models.User` instance.
|
||||
* `request.auth` will be a `djangorestframework.models.OAuthToken` instance.
|
||||
* `request.auth` will be a `rest_framework.models.OAuthToken` instance.
|
||||
|
||||
## SessionAuthentication
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<a class="github" href="negotiation.py"></a>
|
||||
|
||||
# Content negotiation
|
||||
|
||||
> HTTP has provisions for several mechanisms for "content negotiation" - the process of selecting the best representation for a given response when there are multiple representations available.
|
||||
|
|
|
@ -27,9 +27,9 @@ Object level permissions are run by REST framework's generic views when `.get_ob
|
|||
|
||||
The default permission policy may be set globally, using the `DEFAULT_PERMISSIONS` setting. For example.
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSIONS': (
|
||||
'djangorestframework.permissions.IsAuthenticated',
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ This allows you to support file uploads from multiple content-types. For exampl
|
|||
|
||||
`request.parsers` may no longer be altered once `request.DATA`, `request.FILES` or `request.POST` have been accessed.
|
||||
|
||||
If you're using the `djangorestframework.views.View` class... **[TODO]**
|
||||
If you're using the `rest_framework.views.View` class... **[TODO]**
|
||||
|
||||
## .stream
|
||||
|
||||
|
@ -63,6 +63,6 @@ You will not typically need to access `request.stream`, unless you're writing a
|
|||
|
||||
`request.authentication` may no longer be altered once `request.user` or `request.auth` have been accessed.
|
||||
|
||||
If you're using the `djangorestframework.views.View` class... **[TODO]**
|
||||
If you're using the `rest_framework.views.View` class... **[TODO]**
|
||||
|
||||
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
|
|
@ -23,8 +23,8 @@ There's no requirement for you to use them, but if you do then the self-describi
|
|||
|
||||
Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
|
||||
|
||||
from djangorestframework.utils import reverse
|
||||
from djangorestframework.views import APIView
|
||||
from rest_framework.utils import reverse
|
||||
from rest_framework.views import APIView
|
||||
|
||||
class MyView(APIView):
|
||||
def get(self, request):
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
>
|
||||
> — [The Zen of Python][cite]
|
||||
|
||||
Configuration for REST framework is all namespaced inside a single Django setting, named `API_SETTINGS`.
|
||||
Configuration for REST framework is all namespaced inside a single Django setting, named `REST_FRAMEWORK`.
|
||||
|
||||
For example your project's `settings.py` file might include something like this:
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERERS': (
|
||||
'djangorestframework.renderers.YAMLRenderer',
|
||||
'rest_framework.renderers.YAMLRenderer',
|
||||
)
|
||||
'DEFAULT_PARSERS': (
|
||||
'djangorestframework.parsers.YAMLParser',
|
||||
'rest_framework.parsers.YAMLParser',
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ For example your project's `settings.py` file might include something like this:
|
|||
If you need to access the values of REST framework's API settings in your project,
|
||||
you should use the `api_settings` object. For example.
|
||||
|
||||
from djangorestframework.settings import api_settings
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
print api_settings.DEFAULT_AUTHENTICATION
|
||||
|
||||
|
@ -37,9 +37,9 @@ A list or tuple of renderer classes, that determines the default set of renderer
|
|||
Default:
|
||||
|
||||
(
|
||||
'djangorestframework.renderers.JSONRenderer',
|
||||
'djangorestframework.renderers.DocumentingHTMLRenderer'
|
||||
'djangorestframework.renderers.TemplateHTMLRenderer'
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.DocumentingHTMLRenderer'
|
||||
'rest_framework.renderers.TemplateHTMLRenderer'
|
||||
)
|
||||
|
||||
## DEFAULT_PARSERS
|
||||
|
@ -49,8 +49,8 @@ A list or tuple of parser classes, that determines the default set of parsers us
|
|||
Default:
|
||||
|
||||
(
|
||||
'djangorestframework.parsers.JSONParser',
|
||||
'djangorestframework.parsers.FormParser'
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.FormParser'
|
||||
)
|
||||
|
||||
## DEFAULT_AUTHENTICATION
|
||||
|
@ -60,8 +60,8 @@ A list or tuple of authentication classes, that determines the default set of au
|
|||
Default:
|
||||
|
||||
(
|
||||
'djangorestframework.authentication.SessionAuthentication',
|
||||
'djangorestframework.authentication.UserBasicAuthentication'
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.UserBasicAuthentication'
|
||||
)
|
||||
|
||||
## DEFAULT_PERMISSIONS
|
||||
|
@ -80,13 +80,13 @@ Default: `()`
|
|||
|
||||
**TODO**
|
||||
|
||||
Default: `djangorestframework.serializers.ModelSerializer`
|
||||
Default: `rest_framework.serializers.ModelSerializer`
|
||||
|
||||
## DEFAULT_PAGINATION_SERIALIZER
|
||||
|
||||
**TODO**
|
||||
|
||||
Default: `djangorestframework.pagination.PaginationSerializer`
|
||||
Default: `rest_framework.pagination.PaginationSerializer`
|
||||
|
||||
## FORMAT_SUFFIX_KWARG
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
Using bare status codes in your responses isn't recommended. REST framework includes a set of named constants that you can use to make more code more obvious and readable.
|
||||
|
||||
from djangorestframework import status
|
||||
from rest_framework import status
|
||||
|
||||
def empty_view(self):
|
||||
content = {'please move along': 'nothing to see here'}
|
||||
|
|
|
@ -29,10 +29,10 @@ If any throttle check fails an `exceptions.Throttled` exception will be raised,
|
|||
|
||||
The default throttling policy may be set globally, using the `DEFAULT_THROTTLES` and `DEFAULT_THROTTLE_RATES` settings. For example.
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_THROTTLES': (
|
||||
'djangorestframework.throttles.AnonThrottle',
|
||||
'djangorestframework.throttles.UserThrottle',
|
||||
'rest_framework.throttles.AnonThrottle',
|
||||
'rest_framework.throttles.UserThrottle',
|
||||
)
|
||||
'DEFAULT_THROTTLE_RATES': {
|
||||
'anon': '100/day',
|
||||
|
@ -76,7 +76,7 @@ The allowed request rate is determined from one of the following (in order of pr
|
|||
|
||||
## UserRateThrottle
|
||||
|
||||
The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticted requests will fall back to using the IP address of the incoming request is used to generate a unique key to throttle against.
|
||||
The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticted requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against.
|
||||
|
||||
The allowed request rate is determined from one of the following (in order of preference).
|
||||
|
||||
|
@ -95,7 +95,7 @@ For example, multiple user throttle rates could be implemented by using the foll
|
|||
|
||||
...and the following settings.
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_THROTTLES': (
|
||||
'example.throttles.BurstRateThrottle',
|
||||
'example.throttles.SustainedRateThrottle',
|
||||
|
@ -106,7 +106,7 @@ For example, multiple user throttle rates could be implemented by using the foll
|
|||
}
|
||||
}
|
||||
|
||||
`UserThrottle` is suitable if you want a simple global rate restriction per-user.
|
||||
`UserThrottle` is suitable if you want simple global rate restrictions per-user.
|
||||
|
||||
## ScopedRateThrottle
|
||||
|
||||
|
@ -124,16 +124,15 @@ For example, given the following views...
|
|||
throttle_scope = 'contacts'
|
||||
...
|
||||
|
||||
|
||||
class UploadView(APIView):
|
||||
throttle_scope = 'uploads'
|
||||
...
|
||||
|
||||
...and the following settings.
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_THROTTLES': (
|
||||
'djangorestframework.throttles.ScopedRateThrottle',
|
||||
'rest_framework.throttles.ScopedRateThrottle',
|
||||
)
|
||||
'DEFAULT_THROTTLE_RATES': {
|
||||
'contacts': '1000/day',
|
||||
|
|
|
@ -17,7 +17,6 @@ REST framework requires the following:
|
|||
|
||||
* Python (2.6, 2.7)
|
||||
* Django (1.3, 1.4, 1.5)
|
||||
* [URLObject][urlobject] (2.0.0+)
|
||||
|
||||
The following packages are optional:
|
||||
|
||||
|
@ -41,20 +40,22 @@ Install using `pip`, including any optional packages you want...
|
|||
pip install -r requirements.txt
|
||||
pip install -r optionals.txt
|
||||
|
||||
Add `djangorestframework` to your `INSTALLED_APPS`.
|
||||
Add `rest_framework` to your `INSTALLED_APPS`.
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'djangorestframework',
|
||||
'rest_framework',
|
||||
)
|
||||
|
||||
If you're intending to use the browserable API you'll want to add REST framework's login and logout views. Add the following to your root `urls.py` file.
|
||||
|
||||
urlpatterns = patterns('',
|
||||
...
|
||||
url(r'^api-auth/', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
)
|
||||
|
||||
Note that the base URL can be whatever you want, but you must include `rest_framework.urls` with the `rest_framework` namespace.
|
||||
|
||||
## Quickstart
|
||||
|
||||
**TODO**
|
||||
|
@ -97,6 +98,7 @@ General guides to using REST framework.
|
|||
|
||||
* [CSRF][csrf]
|
||||
* [Form overloading][formoverloading]
|
||||
* [Working with the Browsable API][browsableapi]
|
||||
* [Contributing to REST framework][contributing]
|
||||
* [Credits][credits]
|
||||
|
||||
|
@ -110,7 +112,7 @@ Build the docs:
|
|||
|
||||
Run the tests:
|
||||
|
||||
./djangorestframework/runtests/runtests.py
|
||||
./rest_framework/runtests/runtests.py
|
||||
|
||||
## License
|
||||
|
||||
|
@ -168,5 +170,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
[csrf]: topics/csrf.md
|
||||
[formoverloading]: topics/formoverloading.md
|
||||
[browsableapi]: topics/browsable-api.md
|
||||
[contributing]: topics/contributing.md
|
||||
[credits]: topics/credits.md
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
<ul class="dropdown-menu">
|
||||
<li><a href="{{ base_url }}/topics/csrf{{ suffix }}">Working with AJAX and CSRF</a></li>
|
||||
<li><a href="{{ base_url }}/topics/formoverloading{{ suffix }}">Browser based PUT, PATCH and DELETE</a></li>
|
||||
<li><a href="{{ base_url }}/topics/browsable-api{{ suffix }}">Working with the browsable API</a></li>
|
||||
<li><a href="{{ base_url }}/topics/contributing{{ suffix }}">Contributing to REST framework</a></li>
|
||||
<li><a href="{{ base_url }}/topics/credits{{ suffix }}">Credits</a></li>
|
||||
</ul>
|
||||
|
@ -117,7 +118,7 @@
|
|||
if (location.hash) shiftWindow();
|
||||
window.addEventListener("hashchange", shiftWindow);
|
||||
|
||||
$('.dropdown-menu').click(function(event) {
|
||||
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
|
|
97
docs/topics/browsable-api.md
Normal file
97
docs/topics/browsable-api.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
# Working with the Browsable API
|
||||
|
||||
API may stand for Application *Programming* Interface, but humans have to be able to read the APIs, too; someone has to do the programming. Django REST Framework supports generating human-friendly HTML output for each resource when the `HTML` format is requested. These pages allow for easy browsing of resources, as well as forms for submitting data to the resources using `POST`, `PUT`, and `DELETE`.
|
||||
|
||||
## URLs
|
||||
|
||||
If you include fully-qualified URLs in your resource output, they will be 'urlized' and made clickable for easy browsing by humans. The `rest_framework` package includes a [`reverse`][drfreverse] helper for this purpose.
|
||||
|
||||
|
||||
## Formats
|
||||
|
||||
By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview].
|
||||
|
||||
|
||||
## Customizing
|
||||
|
||||
To customize the look-and-feel, create a template called `api.html` and add it to your project, eg: `templates/rest_framework/api.html`, that extends the `rest_framework/base.html` template.
|
||||
|
||||
The included browsable API template is built with [Bootstrap (2.1.1)][bootstrap], making it easy to customize the look-and-feel.
|
||||
|
||||
### Theme
|
||||
|
||||
To replace the theme wholesale, add a `bootstrap_theme` block to your `api.html` and insert a `link` to the desired Bootstrap theme css file. This will completely replace the included theme.
|
||||
|
||||
{% block bootstrap_theme %}
|
||||
<link rel="stylesheet" href="/path/to/my/bootstrap.css" type="text/css">
|
||||
{% endblock %}
|
||||
|
||||
A suitable replacement theme can be generated using Bootstrap's [Customize Tool][bcustomize]. Also, there are pre-made themes available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above.
|
||||
|
||||
You can also change the navbar variant, which by default is `navbar-inverse`, using the `bootstrap_navbar_variant` block. The empty `{% block bootstrap_navbar_variant %}{% endblock %}` will use the original Bootstrap navbar style.
|
||||
|
||||
For more specific CSS tweaks, use the `extra_style` block instead.
|
||||
|
||||
|
||||
### Blocks
|
||||
|
||||
All of the blocks available in the browsable API base template that can be used in your `api.html`.
|
||||
|
||||
* `blockbots` - `<meta>` tag that blocks crawlers
|
||||
* `bodyclass` - (empty) class attribute for the `<body>`
|
||||
* `bootstrap_theme` - CSS for the Bootstrap theme
|
||||
* `bootstrap_navbar_variant` - CSS class for the navbar
|
||||
* `branding` - section of the navbar, see [Bootstrap components][bcomponentsnav]
|
||||
* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
|
||||
* `extrastyle` - (empty) extra CSS for the page
|
||||
* `extrahead` - (empty) extra markup for the page `<head>`
|
||||
* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned)
|
||||
* `global_heading` - (empty) Use to insert content below the header but before the breadcrumbs.
|
||||
* `title` - title of the page
|
||||
* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use {{ block.super }} to preserve the authentication links.
|
||||
|
||||
#### Components
|
||||
|
||||
All of the [Bootstrap components][bcomponents] are available.
|
||||
|
||||
##### Tooltips
|
||||
|
||||
The browsable API makes use of the Bootstrap tooltips component. Any element with the `js-tooltip` class and a `title` attribute has that title content displayed in a tooltip on hover after a 1000ms delay.
|
||||
|
||||
|
||||
### Advanced Customization
|
||||
|
||||
#### Context
|
||||
|
||||
The context that's available to the template:
|
||||
|
||||
* `allowed_methods` : A list of methods allowed by the resource
|
||||
* `api_settings` : The API settings
|
||||
* `available_formats` : A list of formats allowed by the resource
|
||||
* `breadcrumblist` : The list of links following the chain of nested resources
|
||||
* `content` : The content of the API response
|
||||
* `description` : The description of the resource, generated from its docstring
|
||||
* `name` : The name of the resource
|
||||
* `post_form` : A form instance for use by the POST form (if allowed)
|
||||
* `put_form` : A form instance for use by the PUT form (if allowed)
|
||||
* `request` : The request object
|
||||
* `response` : The response object
|
||||
* `version` : The version of Django REST Framework
|
||||
* `view` : The view handling the request
|
||||
* `FORMAT_PARAM` : The view can accept a format override
|
||||
* `METHOD_PARAM` : The view can accept a method override
|
||||
|
||||
#### Not using base.html
|
||||
|
||||
For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your site, you can simply choose not to have `api.html` extend `base.html`. Then the page content and capabilities are entirely up to you.
|
||||
|
||||
|
||||
[drfreverse]: ../api-guide/reverse.md
|
||||
[ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/
|
||||
[chromejsonview]: https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc
|
||||
[bootstrap]: http://getbootstrap.com
|
||||
[bcustomize]: http://twitter.github.com/bootstrap/customize.html#variables
|
||||
[bswatch]: http://bootswatch.com/
|
||||
[bcomponents]: http://twitter.github.com/bootstrap/components.html
|
||||
[bcomponentsnav]: http://twitter.github.com/bootstrap/components.html#navbar
|
||||
|
107
docs/topics/changelog.md
Normal file
107
docs/topics/changelog.md
Normal file
|
@ -0,0 +1,107 @@
|
|||
# Release Notes
|
||||
|
||||
## 2.0.0
|
||||
|
||||
**TODO:** Explain REST framework 2.0
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* Supports Django 1.5.
|
||||
* Fixes issues with 'HEAD' method.
|
||||
* Allow views to specify template used by TemplateRenderer
|
||||
* More consistent error responses
|
||||
* Some serializer fixes
|
||||
* Fix internet explorer ajax behaviour
|
||||
* Minor xml and yaml fixes
|
||||
* Improve setup (eg use staticfiles, not the defunct ADMIN_MEDIA_PREFIX)
|
||||
* Sensible absolute URL generation, not using hacky set_script_prefix
|
||||
|
||||
## 0.3.3
|
||||
|
||||
* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
|
||||
* Use `staticfiles` for css files.
|
||||
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
|
||||
* Templates are now nicely namespaced.
|
||||
- Allows easier overriding.
|
||||
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
|
||||
- Too magical. Explict is better than implicit.
|
||||
* Saner template variable autoescaping.
|
||||
* Tider setup.py
|
||||
* Updated for URLObject 2.0
|
||||
* Bugfixes:
|
||||
- Bug with PerUserThrottling when user contains unicode chars.
|
||||
|
||||
## 0.3.2
|
||||
|
||||
* Bugfixes:
|
||||
* Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115)
|
||||
* serialize_model method in serializer.py may cause wrong value (#73)
|
||||
* Fix Error when clicking OPTIONS button (#146)
|
||||
* And many other fixes
|
||||
* Remove short status codes
|
||||
- Zen of Python: "There should be one-- and preferably only one --obvious way to do it."
|
||||
* get_name, get_description become methods on the view - makes them overridable.
|
||||
* Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering
|
||||
|
||||
## 0.3.1
|
||||
|
||||
* [not documented]
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* JSONP Support
|
||||
* Bugfixes, including support for latest markdown release
|
||||
|
||||
## 0.2.4
|
||||
|
||||
* Fix broken IsAdminUser permission.
|
||||
* OPTIONS support.
|
||||
* XMLParser.
|
||||
* Drop mentions of Blog, BitBucket.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
* Fix some throttling bugs.
|
||||
* ``X-Throttle`` header on throttling.
|
||||
* Support for nesting resources on related models.
|
||||
|
||||
## 0.2.2
|
||||
|
||||
* Throttling support complete.
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* Couple of simple bugfixes over 0.2.0
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear.
|
||||
The public API has been massively cleaned up. Expect it to be fairly stable from here on in.
|
||||
|
||||
* ``Resource`` becomes decoupled into ``View`` and ``Resource``, your views should now inherit from ``View``, not ``Resource``.
|
||||
|
||||
* The handler functions on views ``.get() .put() .post()`` etc, no longer have the ``content`` and ``auth`` args.
|
||||
Use ``self.CONTENT`` inside a view to access the deserialized, validated content.
|
||||
Use ``self.user`` inside a view to access the authenticated user.
|
||||
|
||||
* ``allowed_methods`` and ``anon_allowed_methods`` are now defunct. if a method is defined, it's available.
|
||||
The ``permissions`` attribute on a ``View`` is now used to provide generic permissions checking.
|
||||
Use permission classes such as ``FullAnonAccess``, ``IsAuthenticated`` or ``IsUserOrIsAnonReadOnly`` to set the permissions.
|
||||
|
||||
* The ``authenticators`` class becomes ``authentication``. Class names change to ``Authentication``.
|
||||
|
||||
* The ``emitters`` class becomes ``renderers``. Class names change to ``Renderers``.
|
||||
|
||||
* ``ResponseException`` becomes ``ErrorResponse``.
|
||||
|
||||
* The mixin classes have been nicely refactored, the basic mixins are now ``RequestMixin``, ``ResponseMixin``, ``AuthMixin``, and ``ResourceMixin``
|
||||
You can reuse these mixin classes individually without using the ``View`` class.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Initial release.
|
||||
|
|
@ -40,6 +40,7 @@ The following people have helped make REST framework great.
|
|||
* Can Yavuz - [tschan]
|
||||
* Shawn Lewis - [shawnlewis]
|
||||
* Alec Perkins - [alecperkins]
|
||||
* Michael Barrett - [phobologic]
|
||||
|
||||
Many thanks to everyone who's contributed to the project.
|
||||
|
||||
|
@ -102,3 +103,4 @@ To contact the author directly:
|
|||
[tschan]: https://github.com/tschan
|
||||
[shawnlewis]: https://github.com/shawnlewis
|
||||
[alecperkins]: https://github.com/alecperkins
|
||||
[phobologic]: https://github.com/phobologic
|
||||
|
|
52
docs/topics/rest-hypermedia-hateoas.md
Normal file
52
docs/topics/rest-hypermedia-hateoas.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
# REST, Hypermedia & HATEOAS
|
||||
|
||||
> You keep using that word "REST". I do not think it means what you think it means.
|
||||
>
|
||||
> — Mike Amundsen, [REST fest 2012 keynote][cite].
|
||||
|
||||
First off, the disclaimer. The name "Django REST framework" was choosen with a view to making sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs".
|
||||
|
||||
If you are serious about designing a Hypermedia APIs, you should look to resources outside of this documentation to help inform your design choices.
|
||||
|
||||
The following fall into the "required reading" category.
|
||||
|
||||
* Fielding's dissertation - [Architectural Styles and
|
||||
the Design of Network-based Software Architectures][dissertation].
|
||||
* Fielding's "[REST APIs must be hypertext-driven][hypertext-driven]" blog post.
|
||||
* Leonard Richardson & Sam Ruby's [RESTful Web Services][restful-web-services].
|
||||
* Mike Amundsen's [Building Hypermedia APIs with HTML5 and Node][building-hypermedia-apis].
|
||||
* Steve Klabnik's [Designing Hypermedia APIs][designing-hypermedia-apis].
|
||||
* The [Richardson Maturity Model][maturitymodel].
|
||||
|
||||
For a more thorough background, check out Klabnik's [Hypermedia API reading list][readinglist].
|
||||
|
||||
# Building Hypermedia APIs with REST framework
|
||||
|
||||
REST framework is an agnositic Web API toolkit. It does help guide you towards building well-connected APIs, and makes it easy to design appropriate media types, but it does not strictly enforce any particular design style.
|
||||
|
||||
### What REST framework *does* provide.
|
||||
|
||||
It is self evident that REST framework makes it possible to build Hypermedia APIs. The browseable API that it offers is built on HTML - the hypermedia language of the web.
|
||||
|
||||
REST framework also includes [serialization] and [parser]/[renderer] components that make it easy to build appropriate media types, [hyperlinked relations][fields] for building well-connected systems, and great support for [content negotiation][conneg].
|
||||
|
||||
### What REST framework *doesn't* provide.
|
||||
|
||||
What REST framework doesn't do is give you is machine readable hypermedia formats such as [Collection+JSON][collection] by default, or the ability to auto-magically create HATEOAS style APIs. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
|
||||
|
||||
[cite]: http://vimeo.com/channels/restfest/page:2
|
||||
[dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
|
||||
[hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
||||
[restful-web-services]:
|
||||
[building-hypermedia-apis]: …
|
||||
[designing-hypermedia-apis]: http://designinghypermediaapis.com/
|
||||
[restisover]: http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over
|
||||
[readinglist]: http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list
|
||||
[maturitymodel]: http://martinfowler.com/articles/richardsonMaturityModel.html
|
||||
|
||||
[collection]: http://www.amundsen.com/media-types/collection/
|
||||
[serialization]: ../api-guide/serializers.md
|
||||
[parser]: ../api-guide/parsers.md
|
||||
[renderer]: ../api-guide/renderers.md
|
||||
[fields]: ../api-guide/fields.md
|
||||
[conneg]: ../api-guide/content-negotiation.md
|
|
@ -45,11 +45,11 @@ The simplest way to get up and running will probably be to use an `sqlite3` data
|
|||
}
|
||||
}
|
||||
|
||||
We'll also need to add our new `blog` app and the `djangorestframework` app to `INSTALLED_APPS`.
|
||||
We'll also need to add our new `blog` app and the `rest_framework` app to `INSTALLED_APPS`.
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'djangorestframework',
|
||||
'rest_framework',
|
||||
'blog'
|
||||
)
|
||||
|
||||
|
@ -81,10 +81,11 @@ Don't forget to sync the database for the first time.
|
|||
We're going to create a simple Web API that we can use to edit these comment objects with. The first thing we need is a way of serializing and deserializing the objects into representations such as `json`. We do this by declaring serializers, that work very similarly to Django's forms. Create a file in the project named `serializers.py` and add the following.
|
||||
|
||||
from blog import models
|
||||
from djangorestframework import serializers
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class CommentSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(readonly=True)
|
||||
email = serializers.EmailField()
|
||||
content = serializers.CharField(max_length=200)
|
||||
created = serializers.DateTimeField()
|
||||
|
@ -114,8 +115,8 @@ Okay, once we've got a few imports out of the way, we'd better create a few comm
|
|||
|
||||
from blog.models import Comment
|
||||
from blog.serializers import CommentSerializer
|
||||
from djangorestframework.renderers import JSONRenderer
|
||||
from djangorestframework.parsers import JSONParser
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.parsers import JSONParser
|
||||
|
||||
c1 = Comment(email='leila@example.com', content='nothing to say')
|
||||
c2 = Comment(email='tom@example.com', content='foo bar')
|
||||
|
@ -128,13 +129,13 @@ We've now got a few comment instances to play with. Let's take a look at serial
|
|||
|
||||
serializer = CommentSerializer(instance=c1)
|
||||
serializer.data
|
||||
# {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774, tzinfo=<UTC>)}
|
||||
# {'id': 1, 'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774, tzinfo=<UTC>)}
|
||||
|
||||
At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.
|
||||
|
||||
stream = JSONRenderer().render(serializer.data)
|
||||
stream
|
||||
# '{"email": "leila@example.com", "content": "nothing to say", "created": "2012-08-22T16:20:09.822"}'
|
||||
# '{"id": 1, "email": "leila@example.com", "content": "nothing to say", "created": "2012-08-22T16:20:09.822"}'
|
||||
|
||||
Deserialization is similar. First we parse a stream into python native datatypes...
|
||||
|
||||
|
@ -159,9 +160,10 @@ Edit the `blog/views.py` file, and add the following.
|
|||
|
||||
from blog.models import Comment
|
||||
from blog.serializers import CommentSerializer
|
||||
from djangorestframework.renderers import JSONRenderer
|
||||
from djangorestframework.parsers import JSONParser
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.parsers import JSONParser
|
||||
|
||||
|
||||
class JSONResponse(HttpResponse):
|
||||
|
@ -177,6 +179,7 @@ Edit the `blog/views.py` file, and add the following.
|
|||
|
||||
The root of our API is going to be a view that supports listing all the existing comments, or creating a new comment.
|
||||
|
||||
@csrf_exempt
|
||||
def comment_root(request):
|
||||
"""
|
||||
List all comments, or create a new comment.
|
||||
|
@ -194,10 +197,13 @@ The root of our API is going to be a view that supports listing all the existing
|
|||
comment.save()
|
||||
return JSONResponse(serializer.data, status=201)
|
||||
else:
|
||||
return JSONResponse(serializer.error_data, status=400)
|
||||
return JSONResponse(serializer.errors, status=400)
|
||||
|
||||
Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
|
||||
|
||||
We'll also need a view which corrosponds to an individual comment, and can be used to retrieve, update or delete the comment.
|
||||
|
||||
@csrf_exempt
|
||||
def comment_instance(request, pk):
|
||||
"""
|
||||
Retrieve, update or delete a comment instance.
|
||||
|
@ -219,7 +225,7 @@ We'll also need a view which corrosponds to an individual comment, and can be us
|
|||
comment.save()
|
||||
return JSONResponse(serializer.data)
|
||||
else:
|
||||
return JSONResponse(serializer.error_data, status=400)
|
||||
return JSONResponse(serializer.errors, status=400)
|
||||
|
||||
elif request.method == 'DELETE':
|
||||
comment.delete()
|
||||
|
|
|
@ -40,9 +40,9 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On
|
|||
|
||||
from blog.models import Comment
|
||||
from blog.serializers import CommentSerializer
|
||||
from djangorestframework import status
|
||||
from djangorestframework.decorators import api_view
|
||||
from djangorestframework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
@api_view(['GET', 'POST'])
|
||||
def comment_root(request):
|
||||
|
@ -61,7 +61,7 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On
|
|||
comment.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
else:
|
||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.
|
||||
|
@ -87,7 +87,7 @@ Our instance view is an improvement over the previous example. It's a little mo
|
|||
comment.save()
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
elif request.method == 'DELETE':
|
||||
comment.delete()
|
||||
|
@ -112,7 +112,7 @@ and
|
|||
Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from djangorestframework.urlpatterns import format_suffix_patterns
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
urlpatterns = patterns('blogpost.views',
|
||||
url(r'^$', 'comment_root'),
|
||||
|
@ -125,21 +125,27 @@ We don't necessarily need to add these extra url patterns in, but it gives us a
|
|||
|
||||
## How's it looking?
|
||||
|
||||
Go ahead and test the API from the command line, as we did in [tutorial part 1][2]. Everything is working pretty similarly, although we've got some nicer error handling if we send invalid requests.
|
||||
Go ahead and test the API from the command line, as we did in [tutorial part 1][tut-1]. Everything is working pretty similarly, although we've got some nicer error handling if we send invalid requests.
|
||||
|
||||
**TODO: Describe using accept headers, content-type headers, and format suffixed URLs**
|
||||
|
||||
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/][3]."
|
||||
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/][devserver]."
|
||||
|
||||
**Note: Right now the Browseable API only works with the CBV's. Need to fix that.**
|
||||
|
||||
**TODO: Describe browseable API awesomeness**
|
||||
### Browsability
|
||||
|
||||
Because the API chooses a return format based on what the client asks for, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a browser. This allows for the API to be easily browsable and usable by humans.
|
||||
|
||||
See the [browsable api][browseable-api] topic for more information about the browsable API feature and how to customize it.
|
||||
|
||||
|
||||
## What's next?
|
||||
|
||||
In [tutorial part 3][4], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
|
||||
In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
|
||||
|
||||
[json-url]: http://example.com/api/items/4.json
|
||||
[2]: 1-serialization.md
|
||||
[3]: http://127.0.0.1:8000/
|
||||
[4]: 3-class-based-views.md
|
||||
[devserver]: http://127.0.0.1:8000/
|
||||
[browseable-api]: ../topics/browsable-api.md
|
||||
[tut-1]: 1-serialization.md
|
||||
[tut-3]: 3-class-based-views.md
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Tutorial 3: Class Based Views
|
||||
|
||||
We can also write our API views using class based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][1].
|
||||
We can also write our API views using class based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][dry].
|
||||
|
||||
## Rewriting our API using class based views
|
||||
|
||||
|
@ -9,9 +9,9 @@ We'll start by rewriting the root view as a class based view. All this involves
|
|||
from blog.models import Comment
|
||||
from blog.serializers import CommentSerializer
|
||||
from django.http import Http404
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
class CommentRoot(APIView):
|
||||
|
@ -31,8 +31,6 @@ We'll start by rewriting the root view as a class based view. All this involves
|
|||
return Response(serializer.serialized, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.serialized_errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
comment_root = CommentRoot.as_view()
|
||||
|
||||
So far, so good. It looks pretty similar to the previous case, but we've got better seperation between the different HTTP methods. We'll also need to update the instance view.
|
||||
|
||||
class CommentInstance(APIView):
|
||||
|
@ -55,19 +53,31 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
|
|||
comment = self.get_object(pk)
|
||||
serializer = CommentSerializer(request.DATA, instance=comment)
|
||||
if serializer.is_valid():
|
||||
comment = serializer.deserialized
|
||||
comment = serializer.object
|
||||
comment.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, pk, format=None):
|
||||
comment = self.get_object(pk)
|
||||
comment.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
comment_instance = CommentInstance.as_view()
|
||||
|
||||
That's looking good. Again, it's still pretty similar to the function based view right now.
|
||||
|
||||
We'll also need to refactor our URLconf slightly now we're using class based views.
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
from blogpost import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', views.CommentRoot.as_view()),
|
||||
url(r'^(?P<pk>[0-9]+)$', views.CommentInstance.as_view())
|
||||
)
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||
|
||||
Okay, we're done. If you run the development server everything should be working just as before.
|
||||
|
||||
## Using mixins
|
||||
|
@ -80,8 +90,8 @@ Let's take a look at how we can compose our views by using the mixin classes.
|
|||
|
||||
from blog.models import Comment
|
||||
from blog.serializers import CommentSerializer
|
||||
from djangorestframework import mixins
|
||||
from djangorestframework import generics
|
||||
from rest_framework import mixins
|
||||
from rest_framework import generics
|
||||
|
||||
class CommentRoot(mixins.ListModelMixin,
|
||||
mixins.CreateModelMixin,
|
||||
|
@ -95,8 +105,6 @@ Let's take a look at how we can compose our views by using the mixin classes.
|
|||
def post(self, request, *args, **kwargs):
|
||||
return self.create(request, *args, **kwargs)
|
||||
|
||||
comment_root = CommentRoot.as_view()
|
||||
|
||||
We'll take a moment to examine exactly what's happening here - We're building our view using `MultipleObjectBaseView`, and adding in `ListModelMixin` and `CreateModelMixin`.
|
||||
|
||||
The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explictly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far.
|
||||
|
@ -117,8 +125,6 @@ The base class provides the core functionality, and the mixin classes provide th
|
|||
def delete(self, request, *args, **kwargs):
|
||||
return self.destroy(request, *args, **kwargs)
|
||||
|
||||
comment_instance = CommentInstance.as_view()
|
||||
|
||||
Pretty similar. This time we're using the `SingleObjectBaseView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
|
||||
|
||||
## Using generic class based views
|
||||
|
@ -127,26 +133,21 @@ Using the mixin classes we've rewritten the views to use slightly less code than
|
|||
|
||||
from blog.models import Comment
|
||||
from blog.serializers import CommentSerializer
|
||||
from djangorestframework import generics
|
||||
from rest_framework import generics
|
||||
|
||||
|
||||
class CommentRoot(generics.RootAPIView):
|
||||
model = Comment
|
||||
serializer_class = CommentSerializer
|
||||
|
||||
comment_root = CommentRoot.as_view()
|
||||
|
||||
|
||||
class CommentInstance(generics.InstanceAPIView):
|
||||
model = Comment
|
||||
serializer_class = CommentSerializer
|
||||
|
||||
comment_instance = CommentInstance.as_view()
|
||||
|
||||
|
||||
Wow, that's pretty concise. We've got a huge amount for free, and our code looks like good, clean, idomatic Django.
|
||||
|
||||
Next we'll move onto [part 4 of the tutorial][2], where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects.
|
||||
Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects.
|
||||
|
||||
[1]: http://en.wikipedia.org/wiki/Don't_repeat_yourself
|
||||
[2]: 4-authentication-permissions-and-throttling.md
|
||||
[dry]: http://en.wikipedia.org/wiki/Don't_repeat_yourself
|
||||
[tut-4]: 4-authentication-permissions-and-throttling.md
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
[part 5][5]
|
||||
# Tutorial 4: Authentication & Permissions
|
||||
|
||||
[5]: 5-relationships-and-hyperlinked-apis.md
|
||||
Nothing to see here. Onwards to [part 5][tut-5].
|
||||
|
||||
[tut-5]: 5-relationships-and-hyperlinked-apis.md
|
|
@ -1,9 +1,11 @@
|
|||
# Tutorial 5 - Relationships & Hyperlinked APIs
|
||||
|
||||
**TODO**
|
||||
|
||||
* Create BlogPost model
|
||||
* Demonstrate nested relationships
|
||||
* Demonstrate and describe hyperlinked relationships
|
||||
|
||||
[part 6][1]
|
||||
Onwards to [part 6][tut-6].
|
||||
|
||||
[1]: 6-resource-orientated-projects.md
|
||||
[tut-6]: 6-resource-orientated-projects.md
|
||||
|
|
|
@ -1,31 +1,56 @@
|
|||
serializers.py
|
||||
# Tutorial 6 - Resources
|
||||
|
||||
class BlogPostSerializer(URLModelSerializer):
|
||||
class Meta:
|
||||
model = BlogPost
|
||||
Resource classes are just View classes that don't have any handler methods bound to them. The actions on a resource are defined,
|
||||
|
||||
class CommentSerializer(URLModelSerializer):
|
||||
class Meta:
|
||||
model = Comment
|
||||
This allows us to:
|
||||
|
||||
* Encapsulate common behaviour accross a class of views, in a single Resource class.
|
||||
* Seperate out the actions of a Resource from the specfics of how those actions should be bound to a particular set of URLs.
|
||||
|
||||
## Refactoring to use Resources, not Views
|
||||
|
||||
For instance, we can re-write our 4 sets of views into something more compact...
|
||||
|
||||
resources.py
|
||||
|
||||
class BlogPostResource(ModelResource):
|
||||
serializer_class = BlogPostSerializer
|
||||
model = BlogPost
|
||||
permissions = [AdminOrAnonReadonly()]
|
||||
throttles = [AnonThrottle(rate='5/min')]
|
||||
permissions_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
throttle_classes = (throttles.UserRateThrottle,)
|
||||
|
||||
class CommentResource(ModelResource):
|
||||
serializer_class = CommentSerializer
|
||||
model = Comment
|
||||
permissions = [AdminOrAnonReadonly()]
|
||||
throttles = [AnonThrottle(rate='5/min')]
|
||||
permissions_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
throttle_classes = (throttles.UserRateThrottle,)
|
||||
|
||||
Now that we're using Resources rather than Views, we don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls are handled automatically. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file.
|
||||
## Binding Resources to URLs explicitly
|
||||
The handler methods only get bound to the actions when we define the URLConf. Here's our urls.py:
|
||||
|
||||
comment_root = CommentResource.as_view(actions={
|
||||
'get': 'list',
|
||||
'post': 'create'
|
||||
})
|
||||
comment_instance = CommentInstance.as_view(actions={
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'delete': 'destroy'
|
||||
})
|
||||
... # And for blog post
|
||||
|
||||
urlpatterns = patterns('blogpost.views',
|
||||
url(r'^$', comment_root),
|
||||
url(r'^(?P<pk>[0-9]+)$', comment_instance)
|
||||
... # And for blog post
|
||||
)
|
||||
|
||||
## Using Routers
|
||||
|
||||
Right now that hasn't really saved us a lot of code. However, now that we're using Resources rather than Views, we actually don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using `Router` classes. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file.
|
||||
|
||||
from blog import resources
|
||||
from djangorestframework.routers import DefaultRouter
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(resources.BlogPostResource)
|
||||
|
@ -44,6 +69,8 @@ We've reached the end of our tutorial. If you want to get more involved in the
|
|||
|
||||
* Contribute on GitHub by reviewing issues, and submitting issues or pull requests.
|
||||
* Join the REST framework group, and help build the community.
|
||||
* Follow me [on Twitter](https://twitter.com/_tomchristie) and say hi.
|
||||
* Follow me [on Twitter][twitter] and say hi.
|
||||
|
||||
Now go build something great.
|
||||
**Now go build some awesome things.**
|
||||
|
||||
[twitter]: https://twitter.com/_tomchristie
|
||||
|
|
|
@ -24,7 +24,7 @@ else:
|
|||
|
||||
main_header = '<li class="main"><a href="#{{ anchor }}">{{ title }}</a></li>'
|
||||
sub_header = '<li><a href="#{{ anchor }}">{{ title }}</a></li>'
|
||||
code_label = r'<a class="github" href="https://github.com/tomchristie/django-rest-framework/blob/restframework2/djangorestframework/\1"><span class="label label-info">\1</span></a>'
|
||||
code_label = r'<a class="github" href="https://github.com/tomchristie/django-rest-framework/blob/restframework2/rest_framework/\1"><span class="label label-info">\1</span></a>'
|
||||
|
||||
page = open(os.path.join(docs_dir, 'template.html'), 'r').read()
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
Django>=1.3
|
||||
URLObject>=2.0.0
|
||||
|
|
|
@ -5,8 +5,8 @@ Authentication behavior is provided by mixing the :class:`mixins.RequestMixin` c
|
|||
"""
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
from djangorestframework.compat import CsrfViewMiddleware
|
||||
from djangorestframework.authtoken.models import Token
|
||||
from rest_framework.compat import CsrfViewMiddleware
|
||||
from rest_framework.authtoken.models import Token
|
||||
import base64
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ except ImportError:
|
|||
# from django.utils.decorators import classonlymethod
|
||||
|
||||
# logger = getLogger('django.request') - We'll just drop support for logger if running Django <= 1.2
|
||||
# Might be nice to fix this up sometime to allow djangorestframework.compat.View to match 1.3's View more closely
|
||||
# Might be nice to fix this up sometime to allow rest_framework.compat.View to match 1.3's View more closely
|
||||
|
||||
class View(object):
|
||||
"""
|
|
@ -1,6 +1,12 @@
|
|||
from functools import wraps
|
||||
from django.utils.decorators import available_attrs
|
||||
from djangorestframework.views import APIView
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framwork.views import APIView
|
||||
|
||||
|
||||
class LazyViewCreator(object):
|
|
@ -4,7 +4,7 @@ Handled exceptions raised by REST framework.
|
|||
In addition Django's built in 403 and 404 exceptions are handled.
|
||||
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
|
||||
"""
|
||||
from djangorestframework import status
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
class APIException(Exception):
|
||||
|
@ -31,6 +31,14 @@ class PermissionDenied(APIException):
|
|||
self.detail = detail or self.default_detail
|
||||
|
||||
|
||||
class InvalidFormat(APIException):
|
||||
status_code = status.HTTP_404_NOT_FOUND
|
||||
default_detail = "Format suffix '.%s' not found."
|
||||
|
||||
def __init__(self, format, detail=None):
|
||||
self.detail = (detail or self.default_detail) % format
|
||||
|
||||
|
||||
class MethodNotAllowed(APIException):
|
||||
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
default_detail = "Method '%s' not allowed."
|
||||
|
@ -39,6 +47,15 @@ class MethodNotAllowed(APIException):
|
|||
self.detail = (detail or self.default_detail) % method
|
||||
|
||||
|
||||
class NotAcceptable(APIException):
|
||||
status_code = status.HTTP_406_NOT_ACCEPTABLE
|
||||
default_detail = "Could not satisfy the request's Accept header"
|
||||
|
||||
def __init__(self, detail=None, available_renderers=None):
|
||||
self.detail = detail or self.default_detail
|
||||
self.available_renderers = available_renderers
|
||||
|
||||
|
||||
class UnsupportedMediaType(APIException):
|
||||
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
||||
default_detail = "Unsupported media type '%s' in request."
|
|
@ -10,8 +10,8 @@ from django.db import DEFAULT_DB_ALIAS
|
|||
from django.db.models.related import RelatedObject
|
||||
from django.utils.encoding import is_protected_type, smart_unicode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from djangorestframework.compat import parse_date, parse_datetime
|
||||
from djangorestframework.compat import timezone
|
||||
from rest_framework.compat import parse_date, parse_datetime
|
||||
from rest_framework.compat import timezone
|
||||
|
||||
|
||||
def is_simple_callable(obj):
|
||||
|
@ -42,7 +42,11 @@ class Field(object):
|
|||
|
||||
self.source = source
|
||||
self.readonly = readonly
|
||||
self.required = not(readonly)
|
||||
if required is None:
|
||||
self.required = not(readonly)
|
||||
else:
|
||||
assert not readonly, "Cannot set required=True and readonly=True"
|
||||
self.required = required
|
||||
|
||||
messages = {}
|
||||
for c in reversed(self.__class__.__mro__):
|
||||
|
@ -66,9 +70,8 @@ class Field(object):
|
|||
self.model_field = model_field
|
||||
|
||||
def validate(self, value):
|
||||
pass
|
||||
# if value in validators.EMPTY_VALUES and self.required:
|
||||
# raise ValidationError(self.error_messages['required'])
|
||||
if value in validators.EMPTY_VALUES and self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
|
||||
def run_validators(self, value):
|
||||
if value in validators.EMPTY_VALUES:
|
|
@ -2,7 +2,7 @@
|
|||
Generic views that provide commmonly needed behaviour.
|
||||
"""
|
||||
|
||||
from djangorestframework import views, mixins
|
||||
from rest_framework import views, mixins
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.views.generic.list import MultipleObjectMixin
|
||||
|
|
@ -7,8 +7,8 @@ which allows mixin classes to be composed in interesting ways.
|
|||
Eg. Use mixins to build a Resource class, and have a Router class
|
||||
perform the binding of http methods to actions for us.
|
||||
"""
|
||||
from djangorestframework import status
|
||||
from djangorestframework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class CreateModelMixin(object):
|
||||
|
@ -59,7 +59,7 @@ class UpdateModelMixin(object):
|
|||
self.object = serializer.object
|
||||
self.object.save()
|
||||
return Response(serializer.data)
|
||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class DestroyModelMixin(object):
|
||||
|
@ -84,8 +84,8 @@ class MetadataMixin(object):
|
|||
content = {
|
||||
'name': self.get_name(),
|
||||
'description': self.get_description(),
|
||||
'renders': self._rendered_media_types,
|
||||
'parses': self._parsed_media_types,
|
||||
'renders': [renderer.media_type for renderer in self.renderer_classes],
|
||||
'parses': [parser.media_type for parser in self.parser_classes],
|
||||
}
|
||||
# TODO: Add 'fields', from serializer info.
|
||||
# form = self.get_bound_form()
|
74
rest_framework/negotiation.py
Normal file
74
rest_framework/negotiation.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from rest_framework import exceptions
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils.mediatypes import order_by_precedence
|
||||
|
||||
|
||||
class BaseContentNegotiation(object):
|
||||
def negotiate(self, request, renderers, format=None, force=False):
|
||||
raise NotImplementedError('.negotiate() must be implemented')
|
||||
|
||||
|
||||
class DefaultContentNegotiation(object):
|
||||
settings = api_settings
|
||||
|
||||
def negotiate(self, request, renderers, format=None, force=False):
|
||||
"""
|
||||
Given a request and a list of renderers, return a two-tuple of:
|
||||
(renderer, media type).
|
||||
|
||||
If force is set, then suppress exceptions, and forcibly return a
|
||||
fallback renderer and media_type.
|
||||
"""
|
||||
try:
|
||||
return self.unforced_negotiate(request, renderers, format)
|
||||
except (exceptions.InvalidFormat, exceptions.NotAcceptable):
|
||||
if force:
|
||||
return (renderers[0], renderers[0].media_type)
|
||||
raise
|
||||
|
||||
def unforced_negotiate(self, request, renderers, format=None):
|
||||
"""
|
||||
As `.negotiate()`, but does not take the optional `force` agument,
|
||||
or suppress exceptions.
|
||||
"""
|
||||
# Allow URL style format override. eg. "?format=json
|
||||
format = format or request.GET.get(self.settings.URL_FORMAT_OVERRIDE)
|
||||
|
||||
if format:
|
||||
renderers = self.filter_renderers(renderers, format)
|
||||
|
||||
accepts = self.get_accept_list(request)
|
||||
|
||||
# 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_set in order_by_precedence(accepts):
|
||||
for renderer in renderers:
|
||||
for media_type in media_type_set:
|
||||
if renderer.can_handle_media_type(media_type):
|
||||
return renderer, media_type
|
||||
|
||||
raise exceptions.NotAcceptable(available_renderers=renderers)
|
||||
|
||||
def filter_renderers(self, renderers, format):
|
||||
"""
|
||||
If there is a '.json' style format suffix, filter the renderers
|
||||
so that we only negotiation against those that accept that format.
|
||||
"""
|
||||
renderers = [renderer for renderer in renderers
|
||||
if renderer.can_handle_format(format)]
|
||||
if not renderers:
|
||||
raise exceptions.InvalidFormat(format)
|
||||
return renderers
|
||||
|
||||
def get_accept_list(self, request):
|
||||
"""
|
||||
Given the incoming request, return a tokenised list of media
|
||||
type strings.
|
||||
|
||||
Allows URL style accept override. eg. "?accept=application/json"
|
||||
"""
|
||||
header = request.META.get('HTTP_ACCEPT', '*/*')
|
||||
header = request.GET.get(self.settings.URL_ACCEPT_OVERRIDE, header)
|
||||
return [token.strip() for token in header.split(',')]
|
|
@ -15,28 +15,17 @@ from django.http import QueryDict
|
|||
from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.utils import simplejson as json
|
||||
from djangorestframework.compat import yaml
|
||||
from djangorestframework.exceptions import ParseError
|
||||
from djangorestframework.utils.mediatypes import media_type_matches
|
||||
from rest_framework.compat import yaml
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.utils.mediatypes import media_type_matches
|
||||
from xml.etree import ElementTree as ET
|
||||
from djangorestframework.compat import ETParseError
|
||||
from rest_framework.compat import ETParseError
|
||||
from xml.parsers.expat import ExpatError
|
||||
import datetime
|
||||
import decimal
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseParser',
|
||||
'JSONParser',
|
||||
'PlainTextParser',
|
||||
'FormParser',
|
||||
'MultiPartParser',
|
||||
'YAMLParser',
|
||||
'XMLParser'
|
||||
)
|
||||
|
||||
|
||||
class DataAndFiles(object):
|
||||
def __init__(self, data, files):
|
||||
self.data = data
|
||||
|
@ -45,8 +34,8 @@ class DataAndFiles(object):
|
|||
|
||||
class BaseParser(object):
|
||||
"""
|
||||
All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
|
||||
and overriding the :meth:`parse` method.
|
||||
All parsers should extend `BaseParser`, specifying a `media_type`
|
||||
attribute, and overriding the `.parse_stream()` method.
|
||||
"""
|
||||
|
||||
media_type = None
|
||||
|
@ -77,7 +66,7 @@ class BaseParser(object):
|
|||
|
||||
def parse_stream(self, stream, **opts):
|
||||
"""
|
||||
Given a *stream* to read from, return the deserialized output.
|
||||
Given a stream to read from, return the deserialized output.
|
||||
Should return parsed data, or a DataAndFiles object consisting of the
|
||||
parsed data and files.
|
||||
"""
|
||||
|
@ -245,16 +234,3 @@ class XMLParser(BaseParser):
|
|||
pass
|
||||
|
||||
return value
|
||||
|
||||
|
||||
DEFAULT_PARSERS = (
|
||||
JSONParser,
|
||||
FormParser,
|
||||
MultiPartParser,
|
||||
XMLParser
|
||||
)
|
||||
|
||||
if yaml:
|
||||
DEFAULT_PARSERS += (YAMLParser, )
|
||||
else:
|
||||
YAMLParser = None
|
|
@ -5,15 +5,6 @@ for checking if a request passes a certain set of constraints.
|
|||
Permission behavior is provided by mixing the :class:`mixins.PermissionsMixin` class into a :class:`View` class.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'BasePermission',
|
||||
'FullAnonAccess',
|
||||
'IsAuthenticated',
|
||||
'IsAdminUser',
|
||||
'IsUserOrIsAnonReadOnly',
|
||||
'PerUserThrottling',
|
||||
'PerViewThrottling',
|
||||
)
|
||||
|
||||
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
|
||||
|
|
@ -5,33 +5,18 @@ 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 djangorestframework.compat import yaml
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework.utils import dict2xml
|
||||
from djangorestframework.utils import encoders
|
||||
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
|
||||
from djangorestframework.fields import FloatField, IntegerField, DateTimeField, DateField, EmailField, CharField, BooleanField
|
||||
|
||||
import string
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseRenderer',
|
||||
'TemplateRenderer',
|
||||
'JSONRenderer',
|
||||
'JSONPRenderer',
|
||||
'DocumentingHTMLRenderer',
|
||||
'DocumentingXHTMLRenderer',
|
||||
'DocumentingPlainTextRenderer',
|
||||
'XMLRenderer',
|
||||
'YAMLRenderer'
|
||||
)
|
||||
from rest_framework.compat import yaml
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils import dict2xml
|
||||
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 import serializers
|
||||
|
||||
|
||||
class BaseRenderer(object):
|
||||
|
@ -40,36 +25,28 @@ class BaseRenderer(object):
|
|||
and override the :meth:`render` method.
|
||||
"""
|
||||
|
||||
_FORMAT_QUERY_PARAM = 'format'
|
||||
|
||||
media_type = None
|
||||
format = None
|
||||
|
||||
def __init__(self, view=None):
|
||||
self.view = view
|
||||
|
||||
def can_handle_response(self, accept):
|
||||
"""
|
||||
Returns :const:`True` if this renderer is able to deal with the given
|
||||
*accept* media type.
|
||||
def can_handle_format(self, format):
|
||||
return format == self.format
|
||||
|
||||
The default implementation for this function is to check the *accept*
|
||||
argument against the :attr:`media_type` attribute set on the class to see if
|
||||
def can_handle_media_type(self, media_type):
|
||||
"""
|
||||
Returns `True` if this renderer is able to deal with the given
|
||||
media type.
|
||||
|
||||
The default implementation for this function is to check the media type
|
||||
argument against the media_type attribute set on the class to see if
|
||||
they match.
|
||||
|
||||
This may be overridden to provide for other behavior, but typically you'll
|
||||
instead want to just set the :attr:`media_type` attribute on the class.
|
||||
This may be overridden to provide for other behavior, but typically
|
||||
you'll instead want to just set the `media_type` attribute on the class.
|
||||
"""
|
||||
# TODO: format overriding must go out of here
|
||||
format = None
|
||||
if self.view is not None:
|
||||
format = self.view.kwargs.get(self._FORMAT_QUERY_PARAM, None)
|
||||
if format is None and self.view is not None:
|
||||
format = self.view.request.GET.get(self._FORMAT_QUERY_PARAM, None)
|
||||
|
||||
if format is not None:
|
||||
return format == self.format
|
||||
return media_type_matches(self.media_type, accept)
|
||||
return media_type_matches(self.media_type, media_type)
|
||||
|
||||
def render(self, obj=None, media_type=None):
|
||||
"""
|
||||
|
@ -91,7 +68,7 @@ class BaseRenderer(object):
|
|||
|
||||
class JSONRenderer(BaseRenderer):
|
||||
"""
|
||||
Renderer which serializes to JSON
|
||||
Renderer which serializes to json.
|
||||
"""
|
||||
|
||||
media_type = 'application/json'
|
||||
|
@ -100,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 ''
|
||||
|
@ -115,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)
|
||||
|
||||
|
||||
|
@ -199,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):
|
||||
"""
|
||||
|
@ -217,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]'
|
||||
|
||||
|
@ -238,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
|
||||
|
@ -254,6 +240,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
object = self.view.object
|
||||
serializer = self.view.get_serializer(instance=object)
|
||||
for k, v in serializer.fields.items():
|
||||
if v.readonly:
|
||||
continue
|
||||
fields[k] = field_mapping[v.__class__.__name__]()
|
||||
OnTheFlyForm = type("OnTheFlyForm", (forms.Form,), fields)
|
||||
if object and not self.view.request.method == 'DELETE': # Don't fill in the form when the object is deleted
|
||||
|
@ -280,8 +268,9 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
Add the fields dynamically."""
|
||||
super(GenericContentForm, self).__init__()
|
||||
|
||||
contenttype_choices = [(media_type, media_type) for media_type in view._parsed_media_types]
|
||||
initial_contenttype = view._default_parser.media_type
|
||||
parsed_media_types = [parser.media_type for parser in view.parser_classes]
|
||||
contenttype_choices = [(media_type, media_type) for media_type in parsed_media_types]
|
||||
initial_contenttype = parsed_media_types[0]
|
||||
|
||||
self.fields[request._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type',
|
||||
choices=contenttype_choices,
|
||||
|
@ -317,8 +306,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')
|
||||
|
@ -331,19 +323,17 @@ 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,
|
||||
'breadcrumblist': breadcrumb_list,
|
||||
'allowed_methods': self.view.allowed_methods,
|
||||
'available_formats': self.view._rendered_formats,
|
||||
'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
|
||||
})
|
||||
|
||||
|
@ -356,53 +346,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 = 'djangorestframework/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 = 'djangorestframework/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 = 'djangorestframework/api.txt'
|
||||
|
||||
|
||||
DEFAULT_RENDERERS = (
|
||||
JSONRenderer,
|
||||
JSONPRenderer,
|
||||
DocumentingHTMLRenderer,
|
||||
DocumentingXHTMLRenderer,
|
||||
DocumentingPlainTextRenderer,
|
||||
XMLRenderer
|
||||
)
|
||||
|
||||
if yaml:
|
||||
DEFAULT_RENDERERS += (YAMLRenderer, )
|
||||
else:
|
||||
YAMLRenderer = None
|
|
@ -11,12 +11,9 @@ The wrapped request then offers a richer API, in particular :
|
|||
"""
|
||||
from StringIO import StringIO
|
||||
|
||||
from djangorestframework import exceptions
|
||||
from djangorestframework.settings import api_settings
|
||||
from djangorestframework.utils.mediatypes import is_form_media_type
|
||||
|
||||
|
||||
__all__ = ('Request',)
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils.mediatypes import is_form_media_type
|
||||
|
||||
|
||||
class Empty(object):
|
||||
|
@ -103,6 +100,13 @@ class Request(object):
|
|||
self._load_stream()
|
||||
return self._stream
|
||||
|
||||
@property
|
||||
def QUERY_PARAMS(self):
|
||||
"""
|
||||
More semantically correct name for request.GET.
|
||||
"""
|
||||
return self._request.GET
|
||||
|
||||
@property
|
||||
def DATA(self):
|
||||
"""
|
87
rest_framework/resources.py
Normal file
87
rest_framework/resources.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
from functools import update_wrapper
|
||||
import inspect
|
||||
from django.utils.decorators import classonlymethod
|
||||
from djanorestframework import views, generics
|
||||
|
||||
|
||||
def wrapped(source, dest):
|
||||
"""
|
||||
Copy public, non-method attributes from source to dest, and return dest.
|
||||
"""
|
||||
for attr in [attr for attr in dir(source)
|
||||
if not attr.startswith('_') and not inspect.ismethod(attr)]:
|
||||
setattr(dest, attr, getattr(source, attr))
|
||||
return dest
|
||||
|
||||
|
||||
class ResourceMixin(object):
|
||||
"""
|
||||
Clone Django's `View.as_view()` behaviour *except* using REST framework's
|
||||
'method -> action' binding for resources.
|
||||
"""
|
||||
|
||||
@classonlymethod
|
||||
def as_view(cls, actions, **initkwargs):
|
||||
"""
|
||||
Main entry point for a request-response process.
|
||||
"""
|
||||
# sanitize keyword arguments
|
||||
for key in initkwargs:
|
||||
if key in cls.http_method_names:
|
||||
raise TypeError("You tried to pass in the %s method name as a "
|
||||
"keyword argument to %s(). Don't do that."
|
||||
% (key, cls.__name__))
|
||||
if not hasattr(cls, key):
|
||||
raise TypeError("%s() received an invalid keyword %r" % (
|
||||
cls.__name__, key))
|
||||
|
||||
def view(request, *args, **kwargs):
|
||||
self = cls(**initkwargs)
|
||||
|
||||
# Bind methods to actions
|
||||
for method, action in actions.items():
|
||||
handler = getattr(self, action)
|
||||
setattr(self, method, handler)
|
||||
|
||||
# As you were, solider.
|
||||
if hasattr(self, 'get') and not hasattr(self, 'head'):
|
||||
self.head = self.get
|
||||
return self.dispatch(request, *args, **kwargs)
|
||||
|
||||
# take name and docstring from class
|
||||
update_wrapper(view, cls, updated=())
|
||||
|
||||
# and possible attributes set by decorators
|
||||
# like csrf_exempt from dispatch
|
||||
update_wrapper(view, cls.dispatch, assigned=())
|
||||
return view
|
||||
|
||||
|
||||
class Resource(ResourceMixin, views.APIView):
|
||||
pass
|
||||
|
||||
|
||||
class ModelResource(ResourceMixin, views.APIView):
|
||||
root_class = generics.RootAPIView
|
||||
detail_class = generics.InstanceAPIView
|
||||
|
||||
def root_view(self):
|
||||
return wrapped(self, self.root_class())
|
||||
|
||||
def detail_view(self):
|
||||
return wrapped(self, self.detail_class())
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
return self.root_view().list(request, args, kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
return self.root_view().create(request, args, kwargs)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return self.detail_view().retrieve(request, args, kwargs)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
return self.detail_view().update(request, args, kwargs)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
return self.detail_view().destroy(request, args, kwargs)
|
48
rest_framework/response.py
Normal file
48
rest_framework/response.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||
from django.template.response import SimpleTemplateResponse
|
||||
|
||||
|
||||
class Response(SimpleTemplateResponse):
|
||||
"""
|
||||
An HttpResponse that allows it's data to be rendered into
|
||||
arbitrary media types.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, status=None, headers=None,
|
||||
renderer=None, accepted_media_type=None):
|
||||
"""
|
||||
Alters the init arguments slightly.
|
||||
For example, drop 'template_name', and instead use 'data'.
|
||||
|
||||
Setting 'renderer' and 'media_type' will typically be defered,
|
||||
For example being set automatically by the `APIView`.
|
||||
"""
|
||||
super(Response, self).__init__(None, status=status)
|
||||
self.data = data
|
||||
self.headers = headers and headers[:] or []
|
||||
self.renderer = renderer
|
||||
|
||||
# Accepted media type is the portion of the request Accept header
|
||||
# that the renderer satisfied. It could be '*/*', or somthing like
|
||||
# application/json; indent=4
|
||||
#
|
||||
# This is NOT the value that will be returned in the 'Content-Type'
|
||||
# header, but we do need to know the value in case there are
|
||||
# any specific parameters which affect the rendering process.
|
||||
self.accepted_media_type = accepted_media_type
|
||||
|
||||
@property
|
||||
def rendered_content(self):
|
||||
self['Content-Type'] = self.renderer.media_type
|
||||
if self.data is None:
|
||||
return self.renderer.render()
|
||||
render_media_type = self.accepted_media_type or self.renderer.media_type
|
||||
return self.renderer.render(self.data, render_media_type)
|
||||
|
||||
@property
|
||||
def status_text(self):
|
||||
"""
|
||||
Returns reason text corresponding to our HTTP response status code.
|
||||
Provided for convenience.
|
||||
"""
|
||||
return STATUS_CODE_TEXT.get(self.status_code, '')
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Useful tool to run the test suite for djangorestframework and generate a coverage report.
|
||||
Useful tool to run the test suite for rest_framework and generate a coverage report.
|
||||
"""
|
||||
|
||||
# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
|
||||
|
@ -8,13 +8,13 @@ Useful tool to run the test suite for djangorestframework and generate a coverag
|
|||
# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
|
||||
import os
|
||||
import sys
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangorestframework.runtests.settings'
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings'
|
||||
|
||||
from coverage import coverage
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the tests for djangorestframework and generate a coverage report."""
|
||||
"""Run the tests for rest_framework and generate a coverage report."""
|
||||
|
||||
cov = coverage()
|
||||
cov.erase()
|
||||
|
@ -32,16 +32,16 @@ def main():
|
|||
'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
|
||||
DeprecationWarning
|
||||
)
|
||||
failures = TestRunner(['djangorestframework'])
|
||||
failures = TestRunner(['rest_framework'])
|
||||
else:
|
||||
test_runner = TestRunner()
|
||||
failures = test_runner.run_tests(['djangorestframework'])
|
||||
failures = test_runner.run_tests(['rest_framework'])
|
||||
cov.stop()
|
||||
|
||||
# Discover the list of all modules that we should test coverage for
|
||||
import djangorestframework
|
||||
import rest_framework
|
||||
|
||||
project_dir = os.path.dirname(djangorestframework.__file__)
|
||||
project_dir = os.path.dirname(rest_framework.__file__)
|
||||
cov_files = []
|
||||
|
||||
for (path, dirs, files) in os.walk(project_dir):
|
|
@ -5,7 +5,7 @@
|
|||
# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
|
||||
import os
|
||||
import sys
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangorestframework.runtests.settings'
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings'
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.utils import get_runner
|
||||
|
@ -32,7 +32,7 @@ def main():
|
|||
else:
|
||||
print usage()
|
||||
sys.exit(1)
|
||||
failures = test_runner.run_tests(['djangorestframework' + test_case])
|
||||
failures = test_runner.run_tests(['rest_framework' + test_case])
|
||||
|
||||
sys.exit(failures)
|
||||
|
|
@ -89,8 +89,8 @@ INSTALLED_APPS = (
|
|||
# 'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
'djangorestframework',
|
||||
'djangorestframework.authtoken',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
)
|
||||
|
||||
STATIC_URL = '/static/'
|
|
@ -1,10 +1,10 @@
|
|||
from decimal import Decimal
|
||||
from django.core.serializers.base import DeserializedObject
|
||||
from django.utils.datastructures import SortedDict
|
||||
import copy
|
||||
import datetime
|
||||
import types
|
||||
from djangorestframework.fields import *
|
||||
from decimal import Decimal
|
||||
from django.core.serializers.base import DeserializedObject
|
||||
from django.utils.datastructures import SortedDict
|
||||
from rest_framework.fields import *
|
||||
|
||||
|
||||
class DictWithMetadata(dict):
|
||||
|
@ -95,7 +95,7 @@ class BaseSerializer(Field):
|
|||
self.context = context or {}
|
||||
|
||||
self.init_data = data
|
||||
self.instance = instance
|
||||
self.object = instance
|
||||
|
||||
self._data = None
|
||||
self._errors = None
|
||||
|
@ -247,7 +247,7 @@ class BaseSerializer(Field):
|
|||
self._errors = {}
|
||||
attrs = self.restore_fields(data)
|
||||
if not self._errors:
|
||||
return self.restore_object(attrs, instance=getattr(self, 'instance', None))
|
||||
return self.restore_object(attrs, instance=getattr(self, 'object', None))
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
|
@ -267,7 +267,7 @@ class BaseSerializer(Field):
|
|||
@property
|
||||
def data(self):
|
||||
if self._data is None:
|
||||
self._data = self.to_native(self.instance)
|
||||
self._data = self.to_native(self.object)
|
||||
return self._data
|
||||
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
"""
|
||||
Settings for REST framework are all namespaced in the API_SETTINGS setting.
|
||||
Settings for REST framework are all namespaced in the REST_FRAMEWORK setting.
|
||||
For example your project's `settings.py` file might look like this:
|
||||
|
||||
API_SETTINGS = {
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERERS': (
|
||||
'djangorestframework.renderers.JSONRenderer',
|
||||
'djangorestframework.renderers.YAMLRenderer',
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.YAMLRenderer',
|
||||
)
|
||||
'DEFAULT_PARSERS': (
|
||||
'djangorestframework.parsers.JSONParser',
|
||||
'djangorestframework.parsers.YAMLParser',
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.YAMLParser',
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -23,21 +23,22 @@ from django.utils import importlib
|
|||
|
||||
DEFAULTS = {
|
||||
'DEFAULT_RENDERERS': (
|
||||
'djangorestframework.renderers.JSONRenderer',
|
||||
'djangorestframework.renderers.JSONPRenderer',
|
||||
'djangorestframework.renderers.DocumentingHTMLRenderer',
|
||||
'djangorestframework.renderers.DocumentingPlainTextRenderer',
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
'rest_framework.renderers.DocumentingHTMLRenderer',
|
||||
),
|
||||
'DEFAULT_PARSERS': (
|
||||
'djangorestframework.parsers.JSONParser',
|
||||
'djangorestframework.parsers.FormParser'
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.FormParser'
|
||||
'rest_framework.parsers.MultiPartParser'
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION': (
|
||||
'djangorestframework.authentication.SessionAuthentication',
|
||||
'djangorestframework.authentication.UserBasicAuthentication'
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.UserBasicAuthentication'
|
||||
),
|
||||
'DEFAULT_PERMISSIONS': (),
|
||||
'DEFAULT_THROTTLES': (),
|
||||
'DEFAULT_CONTENT_NEGOTIATION':
|
||||
'rest_framework.negotiation.DefaultContentNegotiation',
|
||||
|
||||
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
|
||||
'UNAUTHENTICATED_TOKEN': None,
|
||||
|
@ -46,6 +47,7 @@ DEFAULTS = {
|
|||
'FORM_CONTENT_OVERRIDE': '_content',
|
||||
'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
|
||||
'URL_ACCEPT_OVERRIDE': '_accept',
|
||||
'URL_FORMAT_OVERRIDE': 'format',
|
||||
|
||||
'FORMAT_SUFFIX_KWARG': 'format'
|
||||
}
|
||||
|
@ -58,8 +60,9 @@ IMPORT_STRINGS = (
|
|||
'DEFAULT_AUTHENTICATION',
|
||||
'DEFAULT_PERMISSIONS',
|
||||
'DEFAULT_THROTTLES',
|
||||
'DEFAULT_CONTENT_NEGOTIATION',
|
||||
'UNAUTHENTICATED_USER',
|
||||
'UNAUTHENTICATED_TOKEN'
|
||||
'UNAUTHENTICATED_TOKEN',
|
||||
)
|
||||
|
||||
|
||||
|
@ -68,7 +71,7 @@ def perform_import(val, setting):
|
|||
If the given setting is a string import notation,
|
||||
then perform the necessary import or imports.
|
||||
"""
|
||||
if val is None or setting not in IMPORT_STRINGS:
|
||||
if val is None or not setting in IMPORT_STRINGS:
|
||||
return val
|
||||
|
||||
if isinstance(val, basestring):
|
||||
|
@ -88,10 +91,7 @@ def import_from_string(val, setting):
|
|||
module_path, class_name = '.'.join(parts[:-1]), parts[-1]
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, class_name)
|
||||
except Exception, e:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
import pdb; pdb.set_trace()
|
||||
except:
|
||||
msg = "Could not import '%s' for API setting '%s'" % (val, setting)
|
||||
raise ImportError(msg)
|
||||
|
||||
|
@ -101,7 +101,7 @@ class APISettings(object):
|
|||
A settings object, that allows API settings to be accessed as properties.
|
||||
For example:
|
||||
|
||||
from djangorestframework.settings import api_settings
|
||||
from rest_framework.settings import api_settings
|
||||
print api_settings.DEFAULT_RENDERERS
|
||||
|
||||
Any setting with string import paths will be automatically resolved
|
||||
|
@ -113,7 +113,7 @@ class APISettings(object):
|
|||
|
||||
try:
|
||||
# Check if present in user settings
|
||||
val = perform_import(settings.API_SETTINGS[attr], attr)
|
||||
val = perform_import(settings.REST_FRAMEWORK[attr], attr)
|
||||
except (AttributeError, KeyError):
|
||||
# Fall back to defaults
|
||||
val = perform_import(DEFAULTS[attr], attr)
|
22
rest_framework/static/rest_framework/css/bootstrap-tweaks.css
vendored
Normal file
22
rest_framework/static/rest_framework/css/bootstrap-tweaks.css
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
|
||||
This CSS file contains some tweaks specific to the included Bootstrap theme.
|
||||
It's separate from `style.css` so that it can be easily overridden by replacing
|
||||
a single block in the template.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
.form-actions {
|
||||
background: transparent;
|
||||
border-top-color: transparent;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.navbar-inverse .brand a {
|
||||
color: #999;
|
||||
}
|
||||
.navbar-inverse .brand:hover a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
841
rest_framework/static/rest_framework/css/bootstrap.min.css
vendored
Normal file
841
rest_framework/static/rest_framework/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
71
rest_framework/static/rest_framework/css/default.css
Normal file
71
rest_framework/static/rest_framework/css/default.css
Normal file
|
@ -0,0 +1,71 @@
|
|||
body {
|
||||
padding-top: 0;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
/* The navbar is fixed at >= 980px wide, so add padding to the body to prevent
|
||||
content running up underneath it. */
|
||||
@media (min-width:980px) {
|
||||
body {
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.resource-description, .response-info {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
border-top: 1px solid #eee;
|
||||
margin-top: 2em;
|
||||
padding-top: 1em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.version:before {
|
||||
content: "v";
|
||||
opacity: 0.6;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.format-option {
|
||||
font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
|
||||
}
|
||||
|
||||
.button-form {
|
||||
float: right;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
/* To allow tooltips to work on disabled elements */
|
||||
.disabled-tooltip-shield {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.errorlist {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
word-wrap: normal;
|
||||
white-space: pre;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0px;
|
||||
}
|
30
rest_framework/static/rest_framework/css/prettify.css
Normal file
30
rest_framework/static/rest_framework/css/prettify.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
.com { color: #93a1a1; }
|
||||
.lit { color: #195f91; }
|
||||
.pun, .opn, .clo { color: #93a1a1; }
|
||||
.fun { color: #dc322f; }
|
||||
.str, .atv { color: #D14; }
|
||||
.kwd, .prettyprint .tag { color: #1e347b; }
|
||||
.typ, .atn, .dec, .var { color: teal; }
|
||||
.pln { color: #48484c; }
|
||||
|
||||
.prettyprint {
|
||||
padding: 8px;
|
||||
background-color: #f7f7f9;
|
||||
border: 1px solid #e1e1e8;
|
||||
}
|
||||
.prettyprint.linenums {
|
||||
-webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
-moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
}
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums {
|
||||
margin: 0 0 0 33px; /* IE indents via margin-left */
|
||||
}
|
||||
ol.linenums li {
|
||||
padding-left: 12px;
|
||||
color: #bebec5;
|
||||
line-height: 20px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
7
rest_framework/static/rest_framework/js/bootstrap.min.js
vendored
Normal file
7
rest_framework/static/rest_framework/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
rest_framework/static/rest_framework/js/default.js
Normal file
5
rest_framework/static/rest_framework/js/default.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
prettyPrint();
|
||||
|
||||
$('.js-tooltip').tooltip({
|
||||
delay: 1000
|
||||
});
|
2
rest_framework/static/rest_framework/js/jquery-1.8.1-min.js
vendored
Normal file
2
rest_framework/static/rest_framework/js/jquery-1.8.1-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
28
rest_framework/static/rest_framework/js/prettify-min.js
vendored
Normal file
28
rest_framework/static/rest_framework/js/prettify-min.js
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
|
||||
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
|
||||
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
|
||||
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
|
||||
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
|
||||
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
|
||||
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
|
||||
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
|
||||
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
|
||||
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
|
||||
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
|
||||
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
|
||||
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
|
||||
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
|
||||
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
|
||||
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
|
||||
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
|
||||
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
|
||||
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
|
||||
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
|
||||
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
|
||||
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
|
||||
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
|
||||
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
|
||||
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
|
||||
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
|
||||
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
|
|
@ -1,3 +1,3 @@
|
|||
{% extends "djangorestframework/base.html" %}
|
||||
{% extends "rest_framework/base.html" %}
|
||||
|
||||
{# Override this template in your own templates directory to customize #}
|
195
rest_framework/templates/rest_framework/base.html
Normal file
195
rest_framework/templates/rest_framework/base.html
Normal file
|
@ -0,0 +1,195 @@
|
|||
{% load url from future %}
|
||||
{% load rest_framework %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
|
||||
{% 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"/>
|
||||
<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/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">
|
||||
<span class="brand" href="/">
|
||||
{% block branding %}<a href='http://django-rest-framework.org'>Django REST framework <span class="version">{{ version }}</span></a>{% endblock %}
|
||||
</span>
|
||||
<ul class="nav pull-right">
|
||||
{% block userlinks %}
|
||||
{% 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 request %}</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>{% optional_login request %}</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% 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">›</span>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
<!-- Content -->
|
||||
<div id="content">
|
||||
|
||||
{% if 'GET' in allowed_methods %}
|
||||
<form id="get-form" class="pull-right">
|
||||
<fieldset>
|
||||
<div class="btn-group format-selection">
|
||||
<a class="btn btn-primary js-tooltip" href='{{ request.get_full_path }}' rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
|
||||
|
||||
<button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for format in available_formats %}
|
||||
<li>
|
||||
<a class="js-tooltip format-option" href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}' rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'OPTIONS' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" method="POST" class="pull-right">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
|
||||
<button class="btn btn-info js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'DELETE' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" method="POST" class="pull-right">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
|
||||
<button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="content-main">
|
||||
<div class="page-header"><h1>{{ name }}</h1></div>
|
||||
<p class="resource-description">{{ description }}</p>
|
||||
|
||||
<div class="request-info">
|
||||
<pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
|
||||
<div>
|
||||
<div class="response-info">
|
||||
<pre class="prettyprint"><div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
|
||||
{% for key, val in response.items %}<b>{{ key }}:</b> <span class="lit">{{ val|urlize_quoted_links }}</span>
|
||||
{% endfor %}
|
||||
</div>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
|
||||
</div>
|
||||
|
||||
{% if response.status_code != 403 %}
|
||||
|
||||
{% if 'POST' in allowed_methods %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %} class="form-horizontal">
|
||||
<fieldset>
|
||||
<h2>POST: {{ name }}</h2>
|
||||
{% csrf_token %}
|
||||
{{ post_form.non_field_errors }}
|
||||
{% for field in post_form %}
|
||||
<div class="control-group {% if field.errors %}error{% endif %}">
|
||||
{{ field.label_tag|add_class:"control-label" }}
|
||||
<div class="controls">
|
||||
{{ field }}
|
||||
<span class="help-inline">{{ field.help_text }}</span>
|
||||
{{ field.errors|add_class:"help-block" }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if 'PUT' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %} class="form-horizontal">
|
||||
<fieldset>
|
||||
<h2>PUT: {{ name }}</h2>
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" />
|
||||
{% csrf_token %}
|
||||
{{ put_form.non_field_errors }}
|
||||
{% for field in put_form %}
|
||||
<div class="control-group {% if field.errors %}error{% endif %}">
|
||||
{{ field.label_tag|add_class:"control-label" }}
|
||||
<div class="controls">
|
||||
{{ field }}
|
||||
<span class='help-inline'>{{ field.help_text }}</span>
|
||||
{{ field.errors|add_class:"help-block" }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<!-- END content-main -->
|
||||
|
||||
</div>
|
||||
<!-- END Content -->
|
||||
|
||||
{% 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>
|
||||
|
||||
{% 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 src="{% get_static_prefix %}rest_framework/js/default.js"></script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -3,7 +3,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/style.css'/>
|
||||
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/style.css'/>
|
||||
</head>
|
||||
|
||||
<body class="login">
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
<div id="content" class="colM">
|
||||
<div id="content-main">
|
||||
<form method="post" action="{% url 'djangorestframework:login' %}" id="login-form">
|
||||
<form method="post" action="{% url 'rest_framework:login' %}" id="login-form">
|
||||
{% csrf_token %}
|
||||
<div class="form-row">
|
||||
<label for="id_username">Username:</label> {{ form.username }}
|
|
@ -1,28 +1,24 @@
|
|||
"""
|
||||
Adds the custom filter 'urlize_quoted_links'
|
||||
|
||||
This is identical to the built-in filter 'urlize' with the exception that
|
||||
single and double quotes are permitted as leading or trailing punctuation.
|
||||
|
||||
Almost all of this code is copied verbatim from django.utils.html
|
||||
LEADING_PUNCTUATION and TRAILING_PUNCTUATION have been modified
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from django.http import QueryDict
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
from urlparse import urlsplit, urlunsplit
|
||||
import re
|
||||
import string
|
||||
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.html import escape
|
||||
from django import template
|
||||
register = template.Library()
|
||||
|
||||
# Configuration for urlize() function.
|
||||
|
||||
# Regex for adding classes to html snippets
|
||||
class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
|
||||
|
||||
|
||||
# Bunch of stuff cloned from urlize
|
||||
LEADING_PUNCTUATION = ['(', '<', '<', '"', "'"]
|
||||
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '>', '"', "'"]
|
||||
|
||||
# List of possible strings used for bullets in bulleted lists.
|
||||
DOTS = ['·', '*', '\xe2\x80\xa2', '•', '•', '•']
|
||||
|
||||
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
|
||||
word_split_re = re.compile(r'(\s+)')
|
||||
punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
|
||||
|
@ -35,6 +31,87 @@ hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|
|
|||
trailing_empty_content_re = re.compile(r'(?:<p>(?: |\s|<br \/>)*?</p>\s*)+\Z')
|
||||
|
||||
|
||||
# Helper function for 'add_query_param'
|
||||
def replace_query_param(url, key, val):
|
||||
"""
|
||||
Given a URL and a key/val pair, set or replace an item in the query
|
||||
parameters of the URL, and return the new URL.
|
||||
"""
|
||||
(scheme, netloc, path, query, fragment) = urlsplit(url)
|
||||
query_dict = QueryDict(query).copy()
|
||||
query_dict[key] = val
|
||||
query = query_dict.urlencode()
|
||||
return urlunsplit((scheme, netloc, path, query, fragment))
|
||||
|
||||
|
||||
# And the template tags themselves...
|
||||
|
||||
@register.simple_tag
|
||||
def optional_login(request):
|
||||
"""
|
||||
Include a login snippet if REST framework's login view is in the URLconf.
|
||||
"""
|
||||
try:
|
||||
login_url = reverse('rest_framework:login')
|
||||
except NoReverseMatch:
|
||||
return ''
|
||||
|
||||
snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path)
|
||||
return snippet
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def optional_logout(request):
|
||||
"""
|
||||
Include a logout snippet if REST framework's logout view is in the URLconf.
|
||||
"""
|
||||
try:
|
||||
logout_url = reverse('rest_framework:logout')
|
||||
except NoReverseMatch:
|
||||
return ''
|
||||
|
||||
snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
|
||||
return snippet
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def add_query_param(request, key, val):
|
||||
"""
|
||||
Add a query parameter to the current request url, and return the new url.
|
||||
"""
|
||||
return replace_query_param(request.get_full_path(), key, val)
|
||||
|
||||
|
||||
@register.filter
|
||||
def add_class(value, css_class):
|
||||
"""
|
||||
http://stackoverflow.com/questions/4124220/django-adding-css-classes-when-rendering-form-fields-in-a-template
|
||||
|
||||
Inserts classes into template variables that contain HTML tags,
|
||||
useful for modifying forms without needing to change the Form objects.
|
||||
|
||||
Usage:
|
||||
|
||||
{{ field.label_tag|add_class:"control-label" }}
|
||||
|
||||
In the case of REST Framework, the filter is used to add Bootstrap-specific
|
||||
classes to the forms.
|
||||
"""
|
||||
html = unicode(value)
|
||||
match = class_re.search(html)
|
||||
if match:
|
||||
m = re.search(r'^%s$|^%s\s|\s%s\s|\s%s$' % (css_class, css_class,
|
||||
css_class, css_class),
|
||||
match.group(1))
|
||||
if not m:
|
||||
return mark_safe(class_re.sub(match.group(1) + " " + css_class,
|
||||
html))
|
||||
else:
|
||||
return mark_safe(html.replace('>', ' class="%s">' % css_class, 1))
|
||||
return value
|
||||
|
||||
|
||||
@register.filter
|
||||
def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):
|
||||
"""
|
||||
Converts any URLs in text into clickable links.
|
||||
|
@ -90,13 +167,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)
|
||||
|
||||
|
||||
#urlize_quoted_links.needs_autoescape = True
|
||||
urlize_quoted_links.is_safe = True
|
||||
|
||||
# Register urlize_quoted_links as a custom filter
|
||||
# http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
|
||||
register = template.Library()
|
||||
register.filter(urlize_quoted_links)
|
||||
return mark_safe(u''.join(words))
|
|
@ -7,6 +7,6 @@ modules = [filename.rsplit('.', 1)[0]
|
|||
__test__ = dict()
|
||||
|
||||
for module in modules:
|
||||
exec("from djangorestframework.tests.%s import __doc__ as module_doc" % module)
|
||||
exec("from djangorestframework.tests.%s import *" % module)
|
||||
exec("from rest_framework.tests.%s import __doc__ as module_doc" % module)
|
||||
exec("from rest_framework.tests.%s import *" % module)
|
||||
__test__[module] = module_doc or ""
|
|
@ -5,11 +5,11 @@ from django.test import Client, TestCase
|
|||
from django.utils import simplejson as json
|
||||
from django.http import HttpResponse
|
||||
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework import permissions
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import permissions
|
||||
|
||||
from djangorestframework.authtoken.models import Token
|
||||
from djangorestframework.authentication import TokenAuthentication
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
|
||||
import base64
|
||||
|
||||
|
@ -32,7 +32,7 @@ urlpatterns = patterns('',
|
|||
|
||||
class BasicAuthTests(TestCase):
|
||||
"""Basic authentication"""
|
||||
urls = 'djangorestframework.tests.authentication'
|
||||
urls = 'rest_framework.tests.authentication'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
||||
|
@ -66,7 +66,7 @@ class BasicAuthTests(TestCase):
|
|||
|
||||
class SessionAuthTests(TestCase):
|
||||
"""User session authentication"""
|
||||
urls = 'djangorestframework.tests.authentication'
|
||||
urls = 'rest_framework.tests.authentication'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
||||
|
@ -113,7 +113,7 @@ class SessionAuthTests(TestCase):
|
|||
|
||||
class TokenAuthTests(TestCase):
|
||||
"""Token authentication"""
|
||||
urls = 'djangorestframework.tests.authentication'
|
||||
urls = 'rest_framework.tests.authentication'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
|
@ -1,7 +1,7 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from djangorestframework.utils.breadcrumbs import get_breadcrumbs
|
||||
from djangorestframework.views import APIView
|
||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
||||
class Root(APIView):
|
||||
|
@ -35,7 +35,7 @@ urlpatterns = patterns('',
|
|||
class BreadcrumbTests(TestCase):
|
||||
"""Tests the breadcrumb functionality used by the HTML renderer."""
|
||||
|
||||
urls = 'djangorestframework.tests.breadcrumbs'
|
||||
urls = 'rest_framework.tests.breadcrumbs'
|
||||
|
||||
def test_root_breadcrumbs(self):
|
||||
url = '/'
|
|
@ -1,6 +1,6 @@
|
|||
from django.test import TestCase
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.compat import apply_markdown
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.compat import apply_markdown
|
||||
|
||||
# We check that docstrings get nicely un-indented.
|
||||
DESCRIPTION = """an example docstring
|
|
@ -1,9 +1,9 @@
|
|||
# from django.test import TestCase
|
||||
# from django import forms
|
||||
|
||||
# from djangorestframework.compat import RequestFactory
|
||||
# from djangorestframework.views import View
|
||||
# from djangorestframework.response import Response
|
||||
# from rest_framework.compat import RequestFactory
|
||||
# from rest_framework.views import View
|
||||
# from rest_framework.response import Response
|
||||
|
||||
# import StringIO
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
# """Tests for the mixin module"""
|
||||
# from django.test import TestCase
|
||||
# from djangorestframework import status
|
||||
# from djangorestframework.compat import RequestFactory
|
||||
# from rest_framework import status
|
||||
# from rest_framework.compat import RequestFactory
|
||||
# from django.contrib.auth.models import Group, User
|
||||
# from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
|
||||
# from djangorestframework.resources import ModelResource
|
||||
# from djangorestframework.response import Response, ImmediateResponse
|
||||
# from djangorestframework.tests.models import CustomUser
|
||||
# from djangorestframework.tests.testcases import TestModelsTestCase
|
||||
# from djangorestframework.views import View
|
||||
# from rest_framework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
|
||||
# from rest_framework.resources import ModelResource
|
||||
# from rest_framework.response import Response, ImmediateResponse
|
||||
# from rest_framework.tests.models import CustomUser
|
||||
# from rest_framework.tests.testcases import TestModelsTestCase
|
||||
# from rest_framework.views import View
|
||||
|
||||
|
||||
# class TestModelRead(TestModelsTestCase):
|
|
@ -1,10 +1,10 @@
|
|||
# from django.conf.urls.defaults import patterns, url
|
||||
# from django.forms import ModelForm
|
||||
# from django.contrib.auth.models import Group, User
|
||||
# from djangorestframework.resources import ModelResource
|
||||
# from djangorestframework.views import ListOrCreateModelView, InstanceModelView
|
||||
# from djangorestframework.tests.models import CustomUser
|
||||
# from djangorestframework.tests.testcases import TestModelsTestCase
|
||||
# from rest_framework.resources import ModelResource
|
||||
# from rest_framework.views import ListOrCreateModelView, InstanceModelView
|
||||
# from rest_framework.tests.models import CustomUser
|
||||
# from rest_framework.tests.testcases import TestModelsTestCase
|
||||
|
||||
|
||||
# class GroupResource(ModelResource):
|
||||
|
@ -36,8 +36,8 @@
|
|||
|
||||
|
||||
# class ModelViewTests(TestModelsTestCase):
|
||||
# """Test the model views djangorestframework provides"""
|
||||
# urls = 'djangorestframework.tests.modelviews'
|
||||
# """Test the model views rest_framework provides"""
|
||||
# urls = 'rest_framework.tests.modelviews'
|
||||
|
||||
# def test_creation(self):
|
||||
# """Ensure that a model object can be created"""
|
|
@ -4,7 +4,7 @@ from django.conf.urls.defaults import patterns, url, include
|
|||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from djangorestframework.views import APIView
|
||||
from rest_framework.views import APIView
|
||||
|
||||
# Since oauth2 / django-oauth-plus are optional dependancies, we don't want to
|
||||
# always run these tests.
|
||||
|
@ -27,7 +27,7 @@ else:
|
|||
urlpatterns = patterns('',
|
||||
url(r'^$', oauth_required(ClientView.as_view())),
|
||||
url(r'^oauth/', include('oauth_provider.urls')),
|
||||
url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
|
||||
url(r'^restframework/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
)
|
||||
|
||||
class OAuthTests(TestCase):
|
||||
|
@ -40,7 +40,7 @@ else:
|
|||
* if confirmed, the user is redirected to the third-party website through the callback view
|
||||
* the third-party website is able to retrieve data from the API
|
||||
"""
|
||||
urls = 'djangorestframework.tests.oauthentication'
|
||||
urls = 'rest_framework.tests.oauthentication'
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
|
@ -1,11 +1,11 @@
|
|||
"""Tests for the djangorestframework package setup."""
|
||||
"""Tests for the rest_framework package setup."""
|
||||
from django.test import TestCase
|
||||
import djangorestframework
|
||||
import rest_framework
|
||||
|
||||
class TestVersion(TestCase):
|
||||
"""Simple sanity test to check the VERSION exists"""
|
||||
|
||||
def test_version(self):
|
||||
"""Ensure the VERSION exists."""
|
||||
djangorestframework.VERSION
|
||||
rest_framework.VERSION
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# """
|
||||
# ..
|
||||
# >>> from djangorestframework.parsers import FormParser
|
||||
# >>> from djangorestframework.compat import RequestFactory
|
||||
# >>> from djangorestframework.views import View
|
||||
# >>> from rest_framework.parsers import FormParser
|
||||
# >>> from rest_framework.compat import RequestFactory
|
||||
# >>> from rest_framework.views import View
|
||||
# >>> from StringIO import StringIO
|
||||
# >>> from urllib import urlencode
|
||||
# >>> req = RequestFactory().get('/')
|
||||
|
@ -83,9 +83,9 @@
|
|||
# import httplib, mimetypes
|
||||
# from tempfile import TemporaryFile
|
||||
# from django.test import TestCase
|
||||
# from djangorestframework.compat import RequestFactory
|
||||
# from djangorestframework.parsers import MultiPartParser
|
||||
# from djangorestframework.views import View
|
||||
# from rest_framework.compat import RequestFactory
|
||||
# from rest_framework.parsers import MultiPartParser
|
||||
# from rest_framework.views import View
|
||||
# from StringIO import StringIO
|
||||
#
|
||||
# def encode_multipart_formdata(fields, files):
|
||||
|
@ -134,8 +134,8 @@
|
|||
from StringIO import StringIO
|
||||
from django import forms
|
||||
from django.test import TestCase
|
||||
from djangorestframework.parsers import FormParser
|
||||
from djangorestframework.parsers import XMLParser
|
||||
from rest_framework.parsers import FormParser
|
||||
from rest_framework.parsers import XMLParser
|
||||
import datetime
|
||||
|
||||
|
|
@ -3,12 +3,13 @@ import re
|
|||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||
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, \
|
||||
XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
|
||||
from djangorestframework.parsers import YAMLParser, XMLParser
|
||||
from rest_framework.parsers import YAMLParser, XMLParser
|
||||
|
||||
from StringIO import StringIO
|
||||
import datetime
|
||||
|
@ -84,7 +85,7 @@ urlpatterns = patterns('',
|
|||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
|
||||
url(r'^html$', HTMLView.as_view()),
|
||||
url(r'^html1$', HTMLView1.as_view()),
|
||||
url(r'^api', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
url(r'^api', include('rest_framework.urls', namespace='rest_framework'))
|
||||
)
|
||||
|
||||
|
||||
|
@ -93,7 +94,7 @@ class RendererEndToEndTests(TestCase):
|
|||
End-to-end testing of renderers using an RendererMixin on a generic view.
|
||||
"""
|
||||
|
||||
urls = 'djangorestframework.tests.renderers'
|
||||
urls = 'rest_framework.tests.renderers'
|
||||
|
||||
def test_default_renderer_serializes_content(self):
|
||||
"""If the Accept header is not set the default renderer should serialize the response."""
|
||||
|
@ -169,15 +170,6 @@ class RendererEndToEndTests(TestCase):
|
|||
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)
|
||||
|
||||
|
||||
_flat_repr = '{"foo": ["bar", "baz"]}'
|
||||
_indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
|
||||
|
@ -221,7 +213,7 @@ class JSONPRendererTests(TestCase):
|
|||
Tests specific to the JSONP Renderer
|
||||
"""
|
||||
|
||||
urls = 'djangorestframework.tests.renderers'
|
||||
urls = 'rest_framework.tests.renderers'
|
||||
|
||||
def test_without_callback_with_json_renderer(self):
|
||||
"""
|
||||
|
@ -255,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):
|
|
@ -5,50 +5,38 @@ from django.conf.urls.defaults import patterns
|
|||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.authentication import SessionAuthentication
|
||||
from djangorestframework.utils import RequestFactory
|
||||
from djangorestframework.parsers import (
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.compat import RequestFactory
|
||||
from rest_framework.parsers import (
|
||||
FormParser,
|
||||
MultiPartParser,
|
||||
PlainTextParser,
|
||||
)
|
||||
from djangorestframework.request import Request
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.views import APIView
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
||||
factory = RequestFactory()
|
||||
|
||||
|
||||
class TestMethodOverloading(TestCase):
|
||||
def test_GET_method(self):
|
||||
def test_method(self):
|
||||
"""
|
||||
GET requests identified.
|
||||
Request methods should be same as underlying request.
|
||||
"""
|
||||
request = factory.get('/')
|
||||
request = Request(factory.get('/'))
|
||||
self.assertEqual(request.method, 'GET')
|
||||
|
||||
def test_POST_method(self):
|
||||
"""
|
||||
POST requests identified.
|
||||
"""
|
||||
request = factory.post('/')
|
||||
request = Request(factory.post('/'))
|
||||
self.assertEqual(request.method, 'POST')
|
||||
|
||||
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'})
|
||||
request = Request(factory.post('/', {Request._METHOD_PARAM: 'DELETE'}))
|
||||
self.assertEqual(request.method, 'DELETE')
|
||||
|
||||
|
||||
|
@ -57,14 +45,14 @@ class TestContentParsing(TestCase):
|
|||
"""
|
||||
Ensure request.DATA returns None for GET request with no content.
|
||||
"""
|
||||
request = factory.get('/')
|
||||
request = 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 = factory.head('/')
|
||||
request = Request(factory.head('/'))
|
||||
self.assertEqual(request.DATA, None)
|
||||
|
||||
def test_standard_behaviour_determines_form_content_POST(self):
|
||||
|
@ -72,8 +60,8 @@ class TestContentParsing(TestCase):
|
|||
Ensure request.DATA returns content for POST request with form content.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser, MultiPartParser)
|
||||
request = factory.post('/', data, parser=parsers)
|
||||
request = Request(factory.post('/', data))
|
||||
request.parser_classes = (FormParser, MultiPartParser)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
def test_standard_behaviour_determines_non_form_content_POST(self):
|
||||
|
@ -83,9 +71,8 @@ class TestContentParsing(TestCase):
|
|||
"""
|
||||
content = 'qwerty'
|
||||
content_type = 'text/plain'
|
||||
parsers = (PlainTextParser,)
|
||||
request = factory.post('/', content, content_type=content_type,
|
||||
parsers=parsers)
|
||||
request = Request(factory.post('/', content, content_type=content_type))
|
||||
request.parser_classes = (PlainTextParser,)
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
def test_standard_behaviour_determines_form_content_PUT(self):
|
||||
|
@ -93,17 +80,17 @@ class TestContentParsing(TestCase):
|
|||
Ensure request.DATA returns content for PUT request with form content.
|
||||
"""
|
||||
data = {'qwerty': 'uiop'}
|
||||
parsers = (FormParser, MultiPartParser)
|
||||
|
||||
from django import VERSION
|
||||
|
||||
if VERSION >= (1, 5):
|
||||
from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart
|
||||
request = factory.put('/', encode_multipart(BOUNDARY, data), parsers=parsers,
|
||||
content_type=MULTIPART_CONTENT)
|
||||
request = Request(factory.put('/', encode_multipart(BOUNDARY, data),
|
||||
content_type=MULTIPART_CONTENT))
|
||||
else:
|
||||
request = factory.put('/', data, parsers=parsers)
|
||||
request = Request(factory.put('/', data))
|
||||
|
||||
request.parser_classes = (FormParser, MultiPartParser)
|
||||
self.assertEqual(request.DATA.items(), data.items())
|
||||
|
||||
def test_standard_behaviour_determines_non_form_content_PUT(self):
|
||||
|
@ -113,9 +100,8 @@ class TestContentParsing(TestCase):
|
|||
"""
|
||||
content = 'qwerty'
|
||||
content_type = 'text/plain'
|
||||
parsers = (PlainTextParser, )
|
||||
request = factory.put('/', content, content_type=content_type,
|
||||
parsers=parsers)
|
||||
request = Request(factory.put('/', content, content_type=content_type))
|
||||
request.parser_classes = (PlainTextParser, )
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
def test_overloaded_behaviour_allows_content_tunnelling(self):
|
||||
|
@ -128,8 +114,8 @@ class TestContentParsing(TestCase):
|
|||
Request._CONTENT_PARAM: content,
|
||||
Request._CONTENTTYPE_PARAM: content_type
|
||||
}
|
||||
parsers = (PlainTextParser, )
|
||||
request = factory.post('/', data, parsers=parsers)
|
||||
request = Request(factory.post('/', data))
|
||||
request.parser_classes = (PlainTextParser, )
|
||||
self.assertEqual(request.DATA, content)
|
||||
|
||||
# def test_accessing_post_after_data_form(self):
|
||||
|
@ -231,7 +217,7 @@ urlpatterns = patterns('',
|
|||
|
||||
|
||||
class TestContentParsingWithAuthentication(TestCase):
|
||||
urls = 'djangorestframework.tests.request'
|
||||
urls = 'rest_framework.tests.request'
|
||||
|
||||
def setUp(self):
|
||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
|
@ -1,18 +1,15 @@
|
|||
import json
|
||||
import unittest
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework.response import Response, NotAcceptable
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework import status
|
||||
from djangorestframework.renderers import (
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.renderers import (
|
||||
BaseRenderer,
|
||||
JSONRenderer,
|
||||
DocumentingHTMLRenderer,
|
||||
DEFAULT_RENDERERS
|
||||
DocumentingHTMLRenderer
|
||||
)
|
||||
|
||||
|
||||
|
@ -24,126 +21,6 @@ class MockJsonRenderer(BaseRenderer):
|
|||
media_type = 'application/json'
|
||||
|
||||
|
||||
class TestResponseDetermineRenderer(TestCase):
|
||||
|
||||
def get_response(self, url='', accept_list=[], renderer_classes=[]):
|
||||
kwargs = {}
|
||||
if accept_list is not None:
|
||||
kwargs['HTTP_ACCEPT'] = ','.join(accept_list)
|
||||
request = RequestFactory().get(url, **kwargs)
|
||||
return Response(request=request, renderer_classes=renderer_classes)
|
||||
|
||||
def test_determine_accept_list_accept_header(self):
|
||||
"""
|
||||
Test that determine_accept_list takes the Accept header.
|
||||
"""
|
||||
accept_list = ['application/pickle', 'application/json']
|
||||
response = self.get_response(accept_list=accept_list)
|
||||
self.assertEqual(response._determine_accept_list(), accept_list)
|
||||
|
||||
def test_determine_accept_list_default(self):
|
||||
"""
|
||||
Test that determine_accept_list takes the default renderer if Accept is not specified.
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
accept_list = ['application/pickle', 'application/json']
|
||||
response = self.get_response(url='?_accept=application/x-www-form-urlencoded',
|
||||
accept_list=accept_list)
|
||||
self.assertEqual(response._determine_accept_list(), ['application/x-www-form-urlencoded'])
|
||||
|
||||
def test_determine_renderer(self):
|
||||
"""
|
||||
Test that right renderer is chosen, in the order of Accept list.
|
||||
"""
|
||||
accept_list = ['application/pickle', 'application/json']
|
||||
renderer_classes = (MockPickleRenderer, MockJsonRenderer)
|
||||
response = self.get_response(accept_list=accept_list, renderer_classes=renderer_classes)
|
||||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, 'application/pickle')
|
||||
self.assertTrue(isinstance(renderer, MockPickleRenderer))
|
||||
|
||||
renderer_classes = (MockJsonRenderer, )
|
||||
response = self.get_response(accept_list=accept_list, renderer_classes=renderer_classes)
|
||||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, 'application/json')
|
||||
self.assertTrue(isinstance(renderer, MockJsonRenderer))
|
||||
|
||||
def test_determine_renderer_default(self):
|
||||
"""
|
||||
Test determine renderer when Accept was not specified.
|
||||
"""
|
||||
renderer_classes = (MockPickleRenderer, )
|
||||
response = self.get_response(accept_list=None, renderer_classes=renderer_classes)
|
||||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, '*/*')
|
||||
self.assertTrue(isinstance(renderer, MockPickleRenderer))
|
||||
|
||||
def test_determine_renderer_no_renderer(self):
|
||||
"""
|
||||
Test determine renderer when no renderer can satisfy the Accept list.
|
||||
"""
|
||||
accept_list = ['application/json']
|
||||
renderer_classes = (MockPickleRenderer, )
|
||||
response = self.get_response(accept_list=accept_list, renderer_classes=renderer_classes)
|
||||
self.assertRaises(NotAcceptable, response._determine_renderer)
|
||||
|
||||
|
||||
class TestResponseRenderContent(TestCase):
|
||||
def get_response(self, url='', accept_list=[], content=None, renderer_classes=None):
|
||||
request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
|
||||
return Response(request=request, content=content, renderer_classes=renderer_classes or DEFAULT_RENDERERS)
|
||||
|
||||
def test_render(self):
|
||||
"""
|
||||
Test rendering simple data to json.
|
||||
"""
|
||||
content = {'a': 1, 'b': [1, 2, 3]}
|
||||
content_type = 'application/json'
|
||||
response = self.get_response(accept_list=[content_type], content=content)
|
||||
response = response.render()
|
||||
self.assertEqual(json.loads(response.content), content)
|
||||
self.assertEqual(response['Content-Type'], content_type)
|
||||
|
||||
def test_render_no_renderer(self):
|
||||
"""
|
||||
Test rendering response when no renderer can satisfy accept.
|
||||
"""
|
||||
content = 'bla'
|
||||
content_type = 'weirdcontenttype'
|
||||
response = self.get_response(accept_list=[content_type], content=content)
|
||||
response = response.render()
|
||||
self.assertEqual(response.status_code, 406)
|
||||
self.assertIsNotNone(response.content)
|
||||
|
||||
# def test_render_renderer_raises_ImmediateResponse(self):
|
||||
# """
|
||||
# Test rendering response when renderer raises ImmediateResponse
|
||||
# """
|
||||
# class PickyJSONRenderer(BaseRenderer):
|
||||
# """
|
||||
# A renderer that doesn't make much sense, just to try
|
||||
# out raising an ImmediateResponse
|
||||
# """
|
||||
# media_type = 'application/json'
|
||||
|
||||
# def render(self, obj=None, media_type=None):
|
||||
# raise ImmediateResponse({'error': '!!!'}, status=400)
|
||||
|
||||
# response = self.get_response(
|
||||
# accept_list=['application/json'],
|
||||
# renderers=[PickyJSONRenderer, JSONRenderer]
|
||||
# )
|
||||
# response = response.render()
|
||||
# self.assertEqual(response.status_code, 400)
|
||||
# self.assertEqual(response.content, json.dumps({'error': '!!!'}))
|
||||
|
||||
|
||||
DUMMYSTATUS = status.HTTP_200_OK
|
||||
DUMMYCONTENT = 'dummycontent'
|
||||
|
||||
|
@ -193,7 +70,7 @@ urlpatterns = patterns('',
|
|||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^html$', HTMLView.as_view()),
|
||||
url(r'^html1$', HTMLView1.as_view()),
|
||||
url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework'))
|
||||
url(r'^restframework', include('rest_framework.urls', namespace='rest_framework'))
|
||||
)
|
||||
|
||||
|
||||
|
@ -203,7 +80,7 @@ class RendererIntegrationTests(TestCase):
|
|||
End-to-end testing of renderers using an ResponseMixin on a generic view.
|
||||
"""
|
||||
|
||||
urls = 'djangorestframework.tests.response'
|
||||
urls = 'rest_framework.tests.response'
|
||||
|
||||
def test_default_renderer_serializes_content(self):
|
||||
"""If the Accept header is not set the default renderer should serialize the response."""
|
||||
|
@ -280,21 +157,12 @@ class RendererIntegrationTests(TestCase):
|
|||
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)
|
||||
|
||||
|
||||
class Issue122Tests(TestCase):
|
||||
"""
|
||||
Tests that covers #122.
|
||||
"""
|
||||
urls = 'djangorestframework.tests.response'
|
||||
urls = 'rest_framework.tests.response'
|
||||
|
||||
def test_only_html_renderer(self):
|
||||
"""
|
|
@ -2,10 +2,10 @@ from django.conf.urls.defaults import patterns, url
|
|||
from django.test import TestCase
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from djangorestframework.renderers import JSONRenderer
|
||||
from djangorestframework.reverse import reverse
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.response import Response
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class MyView(APIView):
|
||||
|
@ -28,7 +28,7 @@ class ReverseTests(TestCase):
|
|||
"""
|
||||
Tests for fully qualifed URLs when using `reverse`.
|
||||
"""
|
||||
urls = 'djangorestframework.tests.reverse'
|
||||
urls = 'rest_framework.tests.reverse'
|
||||
|
||||
def test_reversed_urls_are_fully_qualified(self):
|
||||
response = self.client.get('/myview')
|
|
@ -1,6 +1,6 @@
|
|||
import datetime
|
||||
from django.test import TestCase
|
||||
from djangorestframework import serializers
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class Comment(object):
|
|
@ -1,6 +1,6 @@
|
|||
"""Tests for the status module"""
|
||||
from django.test import TestCase
|
||||
from djangorestframework import status
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
class TestStatus(TestCase):
|
|
@ -59,5 +59,5 @@ class SettingsTestCase(TestCase):
|
|||
|
||||
class TestModelsTestCase(SettingsTestCase):
|
||||
def setUp(self, *args, **kwargs):
|
||||
installed_apps = tuple(settings.INSTALLED_APPS) + ('djangorestframework.tests',)
|
||||
installed_apps = tuple(settings.INSTALLED_APPS) + ('rest_framework.tests',)
|
||||
self.settings_manager.set(INSTALLED_APPS=installed_apps)
|
|
@ -6,10 +6,10 @@ from django.test import TestCase
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.views import APIView
|
||||
from djangorestframework.throttling import UserRateThrottle
|
||||
from djangorestframework.response import Response
|
||||
from rest_framework.compat import RequestFactory
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class User3SecRateThrottle(UserRateThrottle):
|
||||
|
@ -37,7 +37,7 @@ class MockView_MinuteThrottling(APIView):
|
|||
|
||||
|
||||
class ThrottlingTests(TestCase):
|
||||
urls = 'djangorestframework.tests.throttling'
|
||||
urls = 'rest_framework.tests.throttling'
|
||||
|
||||
def setUp(self):
|
||||
"""
|
|
@ -1,8 +1,8 @@
|
|||
# from django import forms
|
||||
# from django.db import models
|
||||
# from django.test import TestCase
|
||||
# from djangorestframework.response import ImmediateResponse
|
||||
# from djangorestframework.views import View
|
||||
# from rest_framework.response import ImmediateResponse
|
||||
# from rest_framework.views import View
|
||||
|
||||
|
||||
# class TestDisabledValidations(TestCase):
|
|
@ -4,7 +4,7 @@
|
|||
# from django.test import TestCase
|
||||
# from django.utils import simplejson as json
|
||||
|
||||
# from djangorestframework.views import View
|
||||
# from rest_framework.views import View
|
||||
|
||||
|
||||
# class MockView(View):
|
||||
|
@ -47,13 +47,13 @@
|
|||
# # 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')),
|
||||
# url(r'^restframework/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
# )
|
||||
|
||||
|
||||
# class BaseViewTests(TestCase):
|
||||
# """Test the base view class of djangorestframework"""
|
||||
# urls = 'djangorestframework.tests.views'
|
||||
# """Test the base view class of rest_framework"""
|
||||
# urls = 'rest_framework.tests.views'
|
||||
|
||||
# def test_view_call_final(self):
|
||||
# response = self.client.options('/mock/final/')
|
||||
|
@ -112,17 +112,17 @@
|
|||
|
||||
|
||||
# class ExtraViewsTests(TestCase):
|
||||
# """Test the extra views djangorestframework provides"""
|
||||
# urls = 'djangorestframework.tests.views'
|
||||
# """Test the extra views rest_framework provides"""
|
||||
# urls = 'rest_framework.tests.views'
|
||||
|
||||
# def test_login_view(self):
|
||||
# """Ensure the login view exists"""
|
||||
# response = self.client.get(reverse('djangorestframework:login'))
|
||||
# response = self.client.get(reverse('rest_framework: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(reverse('djangorestframework:logout'))
|
||||
# response = self.client.get(reverse('rest_framework:logout'))
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.cache import cache
|
||||
from djangorestframework.settings import api_settings
|
||||
import time
|
||||
from django.core.cache import cache
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
|
||||
class BaseThrottle(object):
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user