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:
Jamie Matthews 2012-09-26 13:05:21 +01:00
commit 01770c53cd
109 changed files with 2310 additions and 2445 deletions

2
.gitignore vendored
View File

@ -7,7 +7,7 @@ html/
coverage/
build/
dist/
djangorestframework.egg-info/
rest_framework.egg-info/
MANIFEST
!.gitignore

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}&rsaquo;{% 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>

View File

@ -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)

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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.

View File

@ -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',
)
}

View File

@ -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

View File

@ -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):

View File

@ -6,16 +6,16 @@
>
> &mdash; [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

View File

@ -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'}

View File

@ -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',

View File

@ -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

View File

@ -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>

View 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
View 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.

View File

@ -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

View 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.
>
> &mdash; 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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -1,2 +1 @@
Django>=1.3
URLObject>=2.0.0

View File

@ -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

View File

@ -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):
"""

View File

@ -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):

View File

@ -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."

View File

@ -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
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:

View File

@ -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

View File

@ -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()

View 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(',')]

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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):
"""

View 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)

View 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, '')

View File

@ -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):

View File

@ -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)

View File

@ -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/'

View File

@ -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

View File

@ -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)

View 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;
}

File diff suppressed because one or more lines are too long

View 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;
}

View 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

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View 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"}})();

View File

@ -1,3 +1,3 @@
{% extends "djangorestframework/base.html" %}
{% extends "rest_framework/base.html" %}
{# Override this template in your own templates directory to customize #}

View 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">&rsaquo;</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>

View File

@ -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 }}

View File

@ -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 = ['(', '<', '&lt;', '"', "'"]
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;', '"', "'"]
# List of possible strings used for bullets in bulleted lists.
DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
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>(?:&nbsp;|\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))

View File

@ -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 ""

View File

@ -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)

View File

@ -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 = '/'

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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"""

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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):
"""

View File

@ -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')

View File

@ -1,6 +1,6 @@
import datetime
from django.test import TestCase
from djangorestframework import serializers
from rest_framework import serializers
class Comment(object):

View File

@ -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):

View File

@ -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)

View File

@ -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):
"""

View File

@ -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):

View File

@ -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')

View File

@ -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