merged + fixed broken test

This commit is contained in:
Sébastien Piquemal 2012-02-23 09:01:33 +02:00
commit 9da1ae81dc
38 changed files with 349 additions and 217 deletions

View File

@ -30,6 +30,9 @@ Chris Pickett <bunchesofdonald>
Ben Timby <btimby>
Michele Lazzeri <michelelazzeri-nextage>
Camille Harang <mammique>
Paul Oswald <poswald>
Sean C. Farley <scfarley>
Daniel Izquierdo <izquierdo>
THANKS TO:

View File

@ -1,16 +1,19 @@
Release Notes
=============
development
-----------
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.

View File

@ -1,3 +1,3 @@
__version__ = '0.3.3-dev'
__version__ = '0.3.3'
VERSION = __version__ # synonym

View File

@ -457,3 +457,11 @@ except ImportError: # python < 2.7
return decorator
unittest.skip = skip
# reverse_lazy (Django 1.4 onwards)
try:
from django.core.urlresolvers import reverse_lazy
except:
from django.core.urlresolvers import reverse
from django.utils.functional import lazy
reverse_lazy = lazy(reverse, str)

View File

@ -13,7 +13,6 @@ from djangorestframework.renderers import BaseRenderer
from djangorestframework.resources import Resource, FormResource, ModelResource
from djangorestframework.response import Response, ImmediateResponse
from djangorestframework.request import Request
from djangorestframework.utils import as_tuple, allowed_methods
__all__ = (
@ -498,12 +497,12 @@ class PaginatorMixin(object):
"""
Constructs a url used for getting the next/previous urls
"""
url = URLObject.parse(self.request.get_full_path())
url = url.set_query_param('page', page_number)
url = URLObject(self.request.get_full_path())
url = url.set_query_param('page', str(page_number))
limit = self.get_limit()
if limit != self.limit:
url = url.add_query_param('limit', limit)
url = url.set_query_param('limit', str(limit))
return url

View File

@ -379,7 +379,7 @@ class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
media_type = 'text/html'
format = 'html'
template = 'renderer.html'
template = 'djangorestframework/api.html'
class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
@ -391,7 +391,7 @@ class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
media_type = 'application/xhtml+xml'
format = 'xhtml'
template = 'renderer.html'
template = 'djangorestframework/api.html'
class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
@ -403,7 +403,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
media_type = 'text/plain'
format = 'txt'
template = 'renderer.txt'
template = 'djangorestframework/api.txt'
DEFAULT_RENDERERS = (

View File

@ -9,11 +9,9 @@ The wrapped request then offers a richer API, in particular :
- form overloading of HTTP method, content type and content
"""
from django.http import HttpRequest
from djangorestframework.response import ImmediateResponse
from djangorestframework import status
from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
from djangorestframework.utils.mediatypes import is_form_media_type
from djangorestframework.utils import as_tuple
from StringIO import StringIO
@ -105,7 +103,7 @@ class Request(object):
"""
self._content_type = self.META.get('HTTP_CONTENT_TYPE', self.META.get('CONTENT_TYPE', ''))
self._perform_form_overloading()
# if the HTTP method was not overloaded, we take the raw HTTP method
# if the HTTP method was not overloaded, we take the raw HTTP method
if not hasattr(self, '_method'):
self._method = self.request.method

View File

@ -1,10 +1,11 @@
from django import forms
from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
from django.core.urlresolvers import get_urlconf, get_resolver, NoReverseMatch
from django.db import models
from djangorestframework.response import ImmediateResponse
from djangorestframework.reverse import reverse
from djangorestframework.serializer import Serializer, _SkipField
from djangorestframework.utils import as_tuple
from djangorestframework.utils import as_tuple, reverse
class BaseResource(Serializer):
@ -354,7 +355,7 @@ class ModelResource(FormResource):
instance_attrs[param] = attr
try:
return reverse(self.view_callable[0], kwargs=instance_attrs)
return reverse(self.view_callable[0], self.view.request, kwargs=instance_attrs)
except NoReverseMatch:
pass
raise _SkipField

View File

@ -6,13 +6,13 @@ from any view. It is a bit smarter than Django's `HttpResponse`, for it renders
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
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.
`ImmediateResponse` is an exception that inherits from `Response`. It can be used
to abort the request handling (i.e. ``View.get``, ``View.put``, ...),
to abort the request handling (i.e. ``View.get``, ``View.put``, ...),
and immediately returning a response.
"""
@ -31,8 +31,8 @@ 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 simple Python \
Kwargs:
- content(object). The raw content, not yet serialized. This must be simple Python
data that renderers can handle (e.g.: `dict`, `str`, ...)
- renderers(list/tuple). The renderers to use for rendering the response content.
"""
@ -40,16 +40,17 @@ class Response(SimpleTemplateResponse):
_ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
_IGNORE_IE_ACCEPT_HEADER = True
def __init__(self, content=None, status=None, request=None, renderers=None):
def __init__(self, content=None, status=None, request=None, renderers=None, headers=None):
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
# which we don't need
super(Response, self).__init__(None, status=status)
# We need to store our content in raw content to avoid overriding HttpResponse's
# `content` property
self.raw_content = content
self.raw_content = content
self.has_content_body = content is not None
self.request = request
self.headers = headers and headers[:] or []
if renderers is not None:
self.renderers = renderers
@ -64,7 +65,7 @@ class Response(SimpleTemplateResponse):
@property
def rendered_content(self):
"""
The final rendered content. Accessing this attribute triggers the complete rendering cycle :
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()
@ -88,9 +89,9 @@ class Response(SimpleTemplateResponse):
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
2. `Accept` header of the request
If those are useless, a default value is returned instead.
"""

View File

@ -0,0 +1,23 @@
"""
Provide reverse functions that return fully qualified URLs
"""
from django.core.urlresolvers import reverse as django_reverse
from djangorestframework.compat import reverse_lazy as django_reverse_lazy
def reverse(viewname, request, *args, **kwargs):
"""
Do the same as `django.core.urlresolvers.reverse` but using
*request* to build a fully qualified URL.
"""
url = django_reverse(viewname, *args, **kwargs)
return request.build_absolute_uri(url)
def reverse_lazy(viewname, request, *args, **kwargs):
"""
Do the same as `django.core.urlresolvers.reverse_lazy` but using
*request* to build a fully qualified URL.
"""
url = django_reverse_lazy(viewname, *args, **kwargs)
return request.build_absolute_uri(url)

View File

@ -146,7 +146,7 @@ class Serializer(object):
# then the second element of the tuple is the fields to
# set on the related serializer
if isinstance(info, (list, tuple)):
class OnTheFlySerializer(Serializer):
class OnTheFlySerializer(self.__class__):
fields = info
return OnTheFlySerializer

View File

@ -257,7 +257,7 @@ tfoot td {
color: #666;
padding: 2px 5px;
font-size: 11px;
background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x;
background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x;
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
@ -317,11 +317,11 @@ table thead th.sorted a {
}
table thead th.ascending a {
background: url(../img/admin/arrow-up.gif) right .4em no-repeat;
background: url(../../admin/img/admin/arrow-up.gif) right .4em no-repeat;
}
table thead th.descending a {
background: url(../img/admin/arrow-down.gif) right .4em no-repeat;
background: url(../../admin/img/admin/arrow-down.gif) right .4em no-repeat;
}
/* ORDERABLE TABLES */
@ -332,7 +332,7 @@ table.orderable tbody tr td:hover {
table.orderable tbody tr td:first-child {
padding-left: 14px;
background-image: url(../img/admin/nav-bg-grabber.gif);
background-image: url(../../admin/img/admin/nav-bg-grabber.gif);
background-repeat: repeat-y;
}
@ -362,7 +362,7 @@ input[type=text], input[type=password], textarea, select, .vTextField {
/* FORM BUTTONS */
.button, input[type=submit], input[type=button], .submit-row input {
background: white url(../img/admin/nav-bg.gif) bottom repeat-x;
background: white url(../../admin/img/admin/nav-bg.gif) bottom repeat-x;
padding: 3px 5px;
color: black;
border: 1px solid #bbb;
@ -370,31 +370,31 @@ input[type=text], input[type=password], textarea, select, .vTextField {
}
.button:active, input[type=submit]:active, input[type=button]:active {
background-image: url(../img/admin/nav-bg-reverse.gif);
background-image: url(../../admin/img/admin/nav-bg-reverse.gif);
background-position: top;
}
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
background-image: url(../img/admin/nav-bg.gif);
background-image: url(../../admin/img/admin/nav-bg.gif);
background-position: bottom;
opacity: 0.4;
}
.button.default, input[type=submit].default, .submit-row input.default {
border: 2px solid #5b80b2;
background: #7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x;
background: #7CA0C7 url(../../admin/img/admin/default-bg.gif) bottom repeat-x;
font-weight: bold;
color: white;
float: right;
}
.button.default:active, input[type=submit].default:active {
background-image: url(../img/admin/default-bg-reverse.gif);
background-image: url(../../admin/img/admin/default-bg-reverse.gif);
background-position: top;
}
.button[disabled].default, input[type=submit][disabled].default, input[type=button][disabled].default {
background-image: url(../img/admin/default-bg.gif);
background-image: url(../../admin/img/admin/default-bg.gif);
background-position: bottom;
opacity: 0.4;
}
@ -431,7 +431,7 @@ input[type=text], input[type=password], textarea, select, .vTextField {
font-size: 11px;
text-align: left;
font-weight: bold;
background: #7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x;
background: #7CA0C7 url(../../admin/img/admin/default-bg.gif) top left repeat-x;
color: white;
}
@ -453,15 +453,15 @@ ul.messagelist li {
margin: 0 0 3px 0;
border-bottom: 1px solid #ddd;
color: #666;
background: #ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat;
background: #ffc url(../../admin/img/admin/icon_success.gif) 5px .3em no-repeat;
}
ul.messagelist li.warning{
background-image: url(../img/admin/icon_alert.gif);
background-image: url(../../admin/img/admin/icon_alert.gif);
}
ul.messagelist li.error{
background-image: url(../img/admin/icon_error.gif);
background-image: url(../../admin/img/admin/icon_error.gif);
}
.errornote {
@ -471,7 +471,7 @@ ul.messagelist li.error{
margin: 0 0 3px 0;
border: 1px solid red;
color: red;
background: #ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat;
background: #ffc url(../../admin/img/admin/icon_error.gif) 5px .3em no-repeat;
}
ul.errorlist {
@ -486,7 +486,7 @@ ul.errorlist {
margin: 0 0 3px 0;
border: 1px solid red;
color: white;
background: red url(../img/admin/icon_alert.gif) 5px .3em no-repeat;
background: red url(../../admin/img/admin/icon_alert.gif) 5px .3em no-repeat;
}
.errorlist li a {
@ -522,7 +522,7 @@ div.system-message p.system-message-title {
padding: 4px 5px 4px 25px;
margin: 0;
color: red;
background: #ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat;
background: #ffc url(../../admin/img/admin/icon_error.gif) 5px .3em no-repeat;
}
.description {
@ -533,7 +533,7 @@ div.system-message p.system-message-title {
/* BREADCRUMBS */
div.breadcrumbs {
background: white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x;
background: white url(../../admin/img/admin/nav-bg-reverse.gif) 0 -10px repeat-x;
padding: 2px 8px 3px 8px;
font-size: 11px;
color: #999;
@ -546,17 +546,17 @@ div.breadcrumbs {
.addlink {
padding-left: 12px;
background: url(../img/admin/icon_addlink.gif) 0 .2em no-repeat;
background: url(../../admin/img/admin/icon_addlink.gif) 0 .2em no-repeat;
}
.changelink {
padding-left: 12px;
background: url(../img/admin/icon_changelink.gif) 0 .2em no-repeat;
background: url(../../admin/img/admin/icon_changelink.gif) 0 .2em no-repeat;
}
.deletelink {
padding-left: 12px;
background: url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat;
background: url(../../admin/img/admin/icon_deletelink.gif) 0 .25em no-repeat;
}
a.deletelink:link, a.deletelink:visited {
@ -591,14 +591,14 @@ a.deletelink:hover {
.object-tools li {
display: block;
float: left;
background: url(../img/admin/tool-left.gif) 0 0 no-repeat;
background: url(../../admin/img/admin/tool-left.gif) 0 0 no-repeat;
padding: 0 0 0 8px;
margin-left: 2px;
height: 16px;
}
.object-tools li:hover {
background: url(../img/admin/tool-left_over.gif) 0 0 no-repeat;
background: url(../../admin/img/admin/tool-left_over.gif) 0 0 no-repeat;
}
.object-tools a:link, .object-tools a:visited {
@ -607,29 +607,29 @@ a.deletelink:hover {
color: white;
padding: .1em 14px .1em 8px;
height: 14px;
background: #999 url(../img/admin/tool-right.gif) 100% 0 no-repeat;
background: #999 url(../../admin/img/admin/tool-right.gif) 100% 0 no-repeat;
}
.object-tools a:hover, .object-tools li:hover a {
background: #5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat;
background: #5b80b2 url(../../admin/img/admin/tool-right_over.gif) 100% 0 no-repeat;
}
.object-tools a.viewsitelink, .object-tools a.golink {
background: #999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat;
background: #999 url(../../admin/img/admin/tooltag-arrowright.gif) top right no-repeat;
padding-right: 28px;
}
.object-tools a.viewsitelink:hover, .object-tools a.golink:hover {
background: #5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat;
background: #5b80b2 url(../../admin/img/admin/tooltag-arrowright_over.gif) top right no-repeat;
}
.object-tools a.addlink {
background: #999 url(../img/admin/tooltag-add.gif) top right no-repeat;
background: #999 url(../../admin/img/admin/tooltag-add.gif) top right no-repeat;
padding-right: 28px;
}
.object-tools a.addlink:hover {
background: #5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat;
background: #5b80b2 url(../../admin/img/admin/tooltag-add_over.gif) top right no-repeat;
}
/* OBJECT HISTORY */
@ -764,7 +764,7 @@ table#change-history tbody th {
}
#content-related .module h2 {
background: #eee url(../img/admin/nav-bg.gif) bottom left repeat-x;
background: #eee url(../../admin/img/admin/nav-bg.gif) bottom left repeat-x;
color: #666;
}
@ -910,7 +910,7 @@ fieldset.collapsed h2, fieldset.collapsed {
}
fieldset.collapsed h2 {
background-image: url(../img/admin/nav-bg.gif);
background-image: url(../../admin/img/admin/nav-bg.gif);
background-position: bottom left;
color: #999;
}
@ -931,7 +931,7 @@ fieldset.monospace textarea {
.submit-row {
padding: 5px 7px;
text-align: right;
background: white url(../img/admin/nav-bg.gif) 0 100% repeat-x;
background: white url(../../admin/img/admin/nav-bg.gif) 0 100% repeat-x;
border: 1px solid #ccc;
margin: 5px 0;
overflow: hidden;
@ -950,7 +950,7 @@ fieldset.monospace textarea {
}
.submit-row .deletelink {
background: url(../img/admin/icon_deletelink.gif) 0 50% no-repeat;
background: url(../../admin/img/admin/icon_deletelink.gif) 0 50% no-repeat;
padding-left: 14px;
}
@ -1017,7 +1017,7 @@ fieldset.monospace textarea {
color: #666;
padding: 3px 5px;
font-size: 11px;
background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x;
background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x;
border-bottom: 1px solid #ddd;
}
@ -1102,7 +1102,7 @@ fieldset.monospace textarea {
color: #666;
padding: 3px 5px;
border-bottom: 1px solid #ddd;
background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x;
background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x;
}
.inline-group .tabular tr.add-row td {
@ -1113,7 +1113,7 @@ fieldset.monospace textarea {
.inline-group ul.tools a.add,
.inline-group div.add-row a,
.inline-group .tabular tr.add-row td a {
background: url(../img/admin/icon_addlink.gif) 0 50% no-repeat;
background: url(../../admin/img/admin/icon_addlink.gif) 0 50% no-repeat;
padding-left: 14px;
font-size: 11px;
outline: 0; /* Remove dotted border around link */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,2 +0,0 @@
User-agent: *
Disallow: /

View File

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

View File

@ -6,27 +6,35 @@
{% load static %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/>
<title>Django REST framework - {{ name }}</title>
<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>
<body class="{% block bodyclass %}{% endblock %}">
<div id="container">
<div id="header">
<div id="branding">
<h1 id="site-name"><a href='http://django-rest-framework.org'>Django REST framework</a> <span class="version"> v {{ version }}</span></h1>
<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">
{% if user.is_active %}Welcome, {{ user }}.{% if logout_url %} <a href='{{ logout_url }}'>Log out</a>{% endif %}{% else %}Anonymous {% if login_url %}<a href='{{ login_url }}'>Log in</a>{% endif %}{% endif %}
{% block userlinks %}{% 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 %}
@ -123,7 +131,12 @@
{% endif %}
</div>
<!-- END content-main -->
</div>
<!-- END Content -->
{% block footer %}<div id="footer"></div>{% endblock %}
</div>
</body>
</html>

View File

@ -2,7 +2,7 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}css/djangorestframework.css'/>
<link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/style.css'/>
</head>
<body class="login">

View File

@ -4,8 +4,7 @@ register = Library()
def add_query_param(url, param):
(key, sep, val) = param.partition('=')
return unicode(URLObject.parse(url) & (key, val))
return unicode(URLObject(url).with_query(param))
register.filter('add_query_param', add_query_param)

View File

@ -1,29 +1,34 @@
from django.conf.urls.defaults import patterns, url
from django.core.urlresolvers import reverse
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 View
from djangorestframework.response import Response
class MockView(View):
"""Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified"""
permissions = ()
class MyView(View):
"""
Mock resource which simply returns a URL, so that we can ensure
that reversed URLs are fully qualified.
"""
renderers = (JSONRenderer, )
def get(self, request):
return Response(reverse('another'))
return Response(reverse('myview', request))
urlpatterns = patterns('',
url(r'^$', MockView.as_view()),
url(r'^another$', MockView.as_view(), name='another'),
url(r'^myview$', MyView.as_view(), name='myview'),
)
class ReverseTests(TestCase):
"""Tests for """
"""
Tests for fully qualifed URLs when using `reverse`.
"""
urls = 'djangorestframework.tests.reverse'
def test_reversed_urls_are_fully_qualified(self):
response = self.client.get('/')
self.assertEqual(json.loads(response.content), 'http://testserver/another')
response = self.client.get('/myview')
self.assertEqual(json.loads(response.content), 'http://testserver/myview')

View File

@ -46,8 +46,6 @@ class MockResource(ModelResource):
fields = ('foo', 'bar', 'baz')
urlpatterns = patterns('djangorestframework.utils.staticviews',
url(r'^robots.txt$', 'deny_robots'),
url(r'^favicon.ico$', 'favicon'),
url(r'^accounts/login$', 'api_login'),
url(r'^accounts/logout$', 'api_logout'),
url(r'^mock/$', MockView.as_view()),
@ -123,18 +121,6 @@ class ExtraViewsTests(TestCase):
"""Test the extra views djangorestframework provides"""
urls = 'djangorestframework.tests.views'
def test_robots_view(self):
"""Ensure the robots view exists"""
response = self.client.get('/robots.txt')
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'text/plain')
def test_favicon_view(self):
"""Ensure the favicon view exists"""
response = self.client.get('/favicon.ico')
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'image/vnd.microsoft.icon')
def test_login_view(self):
"""Ensure the login view exists"""
response = self.client.get('/accounts/login')

View File

@ -1,16 +1,6 @@
from django.conf.urls.defaults import patterns
from django.conf import settings
urlpatterns = patterns('djangorestframework.utils.staticviews',
(r'robots.txt', 'deny_robots'),
(r'^accounts/login/$', 'api_login'),
(r'^accounts/logout/$', 'api_logout'),
)
# Only serve favicon in production because otherwise chrome users will pretty much
# permanantly have the django-rest-framework favicon whenever they navigate to
# 127.0.0.1:8000 or whatever, which gets annoying
if not settings.DEBUG:
urlpatterns += patterns('djangorestframework.utils.staticviews',
(r'favicon.ico', 'favicon'),
)

View File

@ -1,6 +1,7 @@
import django
from django.utils.encoding import smart_unicode
from django.utils.xmlutils import SimplerXMLGenerator
from django.core.urlresolvers import resolve
from django.core.urlresolvers import resolve, reverse as django_reverse
from django.conf import settings
from djangorestframework.compat import StringIO
@ -180,3 +181,21 @@ class XMLRenderer():
def dict2xml(input):
return XMLRenderer().dict2xml(input)
def reverse(viewname, request, *args, **kwargs):
"""
Do the same as :py:func:`django.core.urlresolvers.reverse` but using
*request* to build a fully qualified URL.
"""
return request.build_absolute_uri(django_reverse(viewname, *args, **kwargs))
if django.VERSION >= (1, 4):
from django.core.urlresolvers import reverse_lazy as django_reverse_lazy
def reverse_lazy(viewname, request, *args, **kwargs):
"""
Do the same as :py:func:`django.core.urlresolvers.reverse_lazy` but using
*request* to build a fully qualified URL.
"""
return request.build_absolute_uri(django_reverse_lazy(viewname, *args, **kwargs))

View File

@ -6,22 +6,13 @@ from django.template import RequestContext
import base64
def deny_robots(request):
return HttpResponse('User-agent: *\nDisallow: /', mimetype='text/plain')
def favicon(request):
data = 'AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLy8tLy8vL3svLy1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy8vLBsvLywkAAAAATkZFS1xUVPqhn57/y8vL0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmVlQ/GxcXiy8vL88vLy4FdVlXzTkZF/2RdXP/Ly8vty8vLtMvLy5DLy8vty8vLxgAAAAAAAAAAAAAAAAAAAABORkUJTkZF4lNMS/+Lh4f/cWtq/05GRf9ORkX/Vk9O/3JtbP+Ef3//Vk9O/2ljYv/Ly8v5y8vLCQAAAAAAAAAAAAAAAE5GRQlORkX2TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/UElI/8PDw5cAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRZZORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vLvQAAAAAAAAAAAAAAAAAAAADLy8tIaWNi805GRf9ORkX/YVpZ/396eV7Ly8t7qaen9lZOTu5ORkX/TkZF/25oZ//Ly8v/y8vLycvLy0gAAAAATkZFSGNcXPpORkX/TkZF/05GRf+ysLDzTkZFe1NLSv6Oior/raur805GRf9ORkX/TkZF/2hiYf+npaX/y8vL5wAAAABORkXnTkZF/05GRf9ORkX/VU1M/8vLy/9PR0b1TkZF/1VNTP/Ly8uQT0dG+E5GRf9ORkX/TkZF/1hRUP3Ly8tmAAAAAE5GRWBORkXkTkZF/05GRf9ORkX/t7a2/355eOpORkX/TkZFkISAf1BORkX/TkZF/05GRf9XT075TkZFZgAAAAAAAAAAAAAAAAAAAABORkXDTkZF/05GRf9lX17/ubi4/8vLy/+2tbT/Yltb/05GRf9ORkX/a2Vk/8vLy5MAAAAAAAAAAAAAAAAAAAAAAAAAAFNLSqNORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vL+cvLyw8AAAAAAAAAAAAAAABORkUSTkZF+U5GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/1BJSP/CwsLmy8vLDwAAAAAAAAAAAAAAAE5GRRJORkXtTkZF9FFJSJ1ORkXJTkZF/05GRf9ORkX/ZF5d9k5GRZ9ORkXtTkZF5HFsaxUAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRQxORkUJAAAAAAAAAABORkXhTkZF/2JbWv7Ly8tgAAAAAAAAAABORkUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRWBORkX2TkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAP9/gAD+P4AA4AOAAMADgADAA4AAwAOAAMMBgACCAIAAAAGAAIBDgADAA4AAwAOAAMADgADAB4AA/H+AAP7/gAA='
return HttpResponse(base64.b64decode(data), mimetype='image/vnd.microsoft.icon')
# BLERGH
# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
# to add ADMIN_MEDIA_PREFIX to the RequestContext. I don't like this but really really want users to not have to
# be making settings changes in order to accomodate django-rest-framework
@csrf_protect
@never_cache
def api_login(request, template_name='api_login.html',
def api_login(request, template_name='djangorestframework/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm):
"""Displays the login form and handles the login action."""
@ -66,5 +57,5 @@ def api_login(request, template_name='api_login.html',
}, context_instance=RequestContext(request))
def api_logout(request, next_page=None, template_name='api_login.html', redirect_field_name=REDIRECT_FIELD_NAME):
def api_logout(request, next_page=None, template_name='djangorestframework/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
return logout(request, next_page, template_name, redirect_field_name)

View File

@ -7,13 +7,12 @@ By setting or modifying class attributes on your view, you change it's predefine
import re
from django.core.urlresolvers import set_script_prefix, get_script_prefix
from django.http import HttpResponse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View as DjangoView, apply_markdown
from djangorestframework.response import Response, ImmediateResponse
from djangorestframework.response import ImmediateResponse
from djangorestframework.mixins import *
from djangorestframework.utils import allowed_methods
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
@ -163,6 +162,9 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
return description
def markup_description(self, description):
"""
Apply HTML markup to the description of this view.
"""
if apply_markdown:
description = apply_markdown(description)
else:
@ -171,11 +173,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
def http_method_not_allowed(self, request, *args, **kwargs):
"""
Return an HTTP 405 error if an operation is called which does not have a handler method.
Return an HTTP 405 error if an operation is called which does not have
a handler method.
"""
raise ImmediateResponse(
{'detail': 'Method \'%s\' not allowed on this resource.' % request.method},
status=status.HTTP_405_METHOD_NOT_ALLOWED)
content = {
'detail': "Method '%s' not allowed on this resource." % request.method
}
raise ImmediateResponse(content, status.HTTP_405_METHOD_NOT_ALLOWED)
def initial(self, request, *args, **kargs):
"""
@ -184,22 +188,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
Required if you want to do things like set `request.upload_handlers` before
the authentication and dispatch handling is run.
"""
# Calls to 'reverse' will not be fully qualified unless we set the
# scheme/host/port here.
self.orig_prefix = get_script_prefix()
if not (self.orig_prefix.startswith('http:') or self.orig_prefix.startswith('https:')):
prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host())
set_script_prefix(prefix + self.orig_prefix)
return request
pass
def final(self, request, response, *args, **kargs):
"""
Returns an `HttpResponse`. This method is a hook for any code that needs to run
after everything else in the view.
"""
# Restore script_prefix.
set_script_prefix(self.orig_prefix)
# Always add these headers.
response['Allow'] = ', '.join(allowed_methods(self))
# sample to allow caching using Vary http header
@ -211,17 +206,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
# all other authentication is CSRF exempt.
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
self.request = request
self.request = self.create_request(request)
self.args = args
self.kwargs = kwargs
try:
# Get a custom request, built form the original request instance
self.request = request = self.create_request(request)
# `initial` is the opportunity to temper with the request,
# even completely replace it.
self.request = request = self.initial(request, *args, **kwargs)
self.initial(request, *args, **kwargs)
# Authenticate and check request has the relevant permissions
self._check_permissions()
@ -231,7 +221,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# TODO: should we enforce HttpResponse, like Django does ?
response = handler(request, *args, **kwargs)
@ -239,7 +229,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
self.response = response = self.prepare_response(response)
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
# TODO: ugly hack to handle both HttpResponse and Response.
# TODO: ugly hack to handle both HttpResponse and Response.
if hasattr(response, 'raw_content'):
response.raw_content = self.filter_response(response.raw_content)
else:

39
docs/howto/reverse.rst Normal file
View File

@ -0,0 +1,39 @@
Returning URIs from your Web APIs
=================================
As a rule, it's probably better practice to return absolute URIs from you web APIs, e.g. "http://example.com/foobar", rather than returning relative URIs, e.g. "/foobar".
The advantages of doing so are:
* It's more explicit.
* It leaves less work for your API clients.
* There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type.
* It allows us to easily do things like markup HTML representations with hyperlinks.
Django REST framework provides two utility functions to make it simpler to return absolute URIs from your Web API.
There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink its output for you, which makes browsing the API much easier.
reverse(viewname, request, ...)
-------------------------------
The :py:func:`~reverse.reverse` function has the same behavior as `django.core.urlresolvers.reverse`_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port::
from djangorestframework.reverse import reverse
from djangorestframework.views import View
class MyView(View):
def get(self, request):
context = {
'url': reverse('year-summary', request, args=[1945])
}
return Response(context)
reverse_lazy(viewname, request, ...)
------------------------------------
The :py:func:`~reverse.reverse_lazy` function has the same behavior as `django.core.urlresolvers.reverse_lazy`_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port.
.. _django.core.urlresolvers.reverse: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
.. _django.core.urlresolvers.reverse_lazy: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy

View File

@ -3,52 +3,70 @@
Setup
=====
Installing into site-packages
-----------------------------
Templates
---------
If you need to manually install Django REST framework to your ``site-packages`` directory, run the ``setup.py`` script::
Django REST framework uses a few templates for the HTML and plain text
documenting renderers. You'll need to ensure ``TEMPLATE_LOADERS`` setting
contains ``'django.template.loaders.app_directories.Loader'``.
This will already be the case by default.
python setup.py install
You may customize the templates by creating a new template called
``djangorestframework/api.html`` in your project, which should extend
``djangorestframework/base.html`` and override the appropriate
block tags. For example::
Template Loaders
----------------
{% extends "djangorestframework/base.html" %}
Django REST framework uses a few templates for the HTML and plain text documenting renderers.
{% block title %}My API{% endblock %}
* Ensure ``TEMPLATE_LOADERS`` setting contains ``'django.template.loaders.app_directories.Loader'``.
{% block branding %}
<h1 id="site-name">My API</h1>
{% endblock %}
This will be the case by default so you shouldn't normally need to do anything here.
Admin Styling
-------------
Styling
-------
Django REST framework uses the admin media for styling. When running using Django's testserver this is automatically served for you,
but once you move onto a production server, you'll want to make sure you serve the admin media separately, exactly as you would do
`if using the Django admin <https://docs.djangoproject.com/en/dev/howto/deployment/modpython/#serving-the-admin-files>`_.
Django REST framework requires `django.contrib.staticfiles`_ to serve it's css.
If you're using Django 1.2 you'll need to use the seperate
`django-staticfiles`_ package instead.
You can override the styling by creating a file in your top-level static
directory named ``djangorestframework/css/style.css``
* Ensure that the ``ADMIN_MEDIA_PREFIX`` is set appropriately and that you are serving the admin media.
(Django's testserver will automatically serve the admin media for you)
Markdown
--------
The Python `markdown library <http://www.freewisdom.org/projects/python-markdown/>`_ is not required but comes recommended.
`Python markdown`_ is not required but comes recommended.
If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting
<http://daringfireball.net/projects/markdown/syntax>`_ which will be rendered by the HTML documenting renderer.
If markdown is installed your :class:`.Resource` descriptions can include
`markdown formatting`_ which will be rendered by the self-documenting API.
robots.txt, favicon, login/logout
---------------------------------
YAML
----
Django REST framework comes with a few views that can be useful including a deny robots view, a favicon view, and api login and logout views::
YAML support is optional, and requires `PyYAML`_.
Login / Logout
--------------
Django REST framework includes login and logout views that are useful if
you're using the self-documenting API::
from django.conf.urls.defaults import patterns
urlpatterns = patterns('djangorestframework.views',
(r'robots.txt', 'deny_robots'),
(r'favicon.ico', 'favicon'),
# Add your resources here
(r'^accounts/login/$', 'api_login'),
(r'^accounts/logout/$', 'api_logout'),
)
.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/
.. _URLObject: http://pypi.python.org/pypi/URLObject/
.. _Python markdown: http://www.freewisdom.org/projects/python-markdown/
.. _markdown formatting: http://daringfireball.net/projects/markdown/syntax
.. _PyYAML: http://pypi.python.org/pypi/PyYAML

View File

@ -40,8 +40,11 @@ Requirements
------------
* Python (2.5, 2.6, 2.7 supported)
* Django (1.2, 1.3, 1.4-alpha supported)
* Django (1.2, 1.3, 1.4 supported)
* `django.contrib.staticfiles`_ (or `django-staticfiles`_ for Django 1.2)
* `URLObject`_ >= 2.0.0
* `Markdown`_ >= 2.1.0 (Optional)
* `PyYAML`_ >= 3.10 (Optional)
Installation
------------
@ -54,8 +57,6 @@ Or get the latest development version using git::
git clone git@github.com:tomchristie/django-rest-framework.git
Or you can `download the current release <http://pypi.python.org/pypi/djangorestframework>`_.
Setup
-----
@ -114,3 +115,8 @@ Indices and tables
* :ref:`modindex`
* :ref:`search`
.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/
.. _URLObject: http://pypi.python.org/pypi/URLObject/
.. _Markdown: http://pypi.python.org/pypi/Markdown/
.. _PyYAML: http://pypi.python.org/pypi/PyYAML

5
docs/library/reverse.rst Normal file
View File

@ -0,0 +1,5 @@
:mod:`reverse`
================
.. automodule:: reverse
:members:

5
docs/library/utils.rst Normal file
View File

@ -0,0 +1,5 @@
:mod:`utils`
==============
.. automodule:: utils
:members:

View File

@ -1,5 +1,5 @@
from django.core.urlresolvers import reverse
from djangorestframework.resources import ModelResource
from djangorestframework.reverse import reverse
from blogpost.models import BlogPost, Comment
@ -12,7 +12,7 @@ class BlogPostResource(ModelResource):
ordering = ('-created',)
def comments(self, instance):
return reverse('comments', kwargs={'blogpost': instance.key})
return reverse('comments', request, kwargs={'blogpost': instance.key})
class CommentResource(ModelResource):
@ -24,4 +24,4 @@ class CommentResource(ModelResource):
ordering = ('-created',)
def blogpost(self, instance):
return reverse('blog-post', kwargs={'key': instance.blogpost.key})
return reverse('blog-post', request, kwargs={'key': instance.blogpost.key})

View File

@ -1,12 +1,11 @@
"""Test a range of REST API usage of the example application.
"""
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory
from djangorestframework.reverse import reverse
from djangorestframework.views import InstanceModelView, ListOrCreateModelView
from blogpost import models, urls

View File

@ -2,9 +2,9 @@ from djangorestframework.compat import View # Use Django 1.3's django.views.gen
from djangorestframework.mixins import ResponseMixin
from djangorestframework.renderers import DEFAULT_RENDERERS
from djangorestframework.response import Response
from djangorestframework.reverse import reverse
from django.conf.urls.defaults import patterns, url
from django.core.urlresolvers import reverse
class ExampleView(ResponseMixin, View):
@ -14,7 +14,7 @@ class ExampleView(ResponseMixin, View):
def get(self, request):
response = Response({'description': 'Some example content',
'url': reverse('mixin-view')}, status=200)
'url': reverse('mixin-view', request)}, status=200)
self.response = self.prepare_response(response)
return self.response

View File

@ -1,6 +1,6 @@
from django.conf import settings
from django.core.urlresolvers import reverse
from djangorestframework.reverse import reverse
from djangorestframework.views import View
from djangorestframework.response import Response
from djangorestframework import status
@ -41,7 +41,7 @@ class ObjectStoreRoot(View):
filepaths = [os.path.join(OBJECT_STORE_DIR, file) for file in os.listdir(OBJECT_STORE_DIR) if not file.startswith('.')]
ctime_sorted_basenames = [item[0] for item in sorted([(os.path.basename(path), os.path.getctime(path)) for path in filepaths],
key=operator.itemgetter(1), reverse=True)]
return Response([reverse('stored-object', kwargs={'key':key}) for key in ctime_sorted_basenames])
return Response([reverse('stored-object', request, kwargs={'key':key}) for key in ctime_sorted_basenames])
def post(self, request):
"""
@ -51,8 +51,8 @@ class ObjectStoreRoot(View):
pathname = os.path.join(OBJECT_STORE_DIR, key)
pickle.dump(self.CONTENT, open(pathname, 'wb'))
remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES)
self.headers['Location'] = reverse('stored-object', kwargs={'key':key})
return Response(self.CONTENT, status=status.HTTP_201_CREATED)
url = reverse('stored-object', request, kwargs={'key':key})
return Response(self.CONTENT, status.HTTP_201_CREATED, {'Location': url})
class StoredObject(View):

View File

@ -1,7 +1,7 @@
from djangorestframework.views import View
from djangorestframework.response import Response
from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
from django.core.urlresolvers import reverse
from djangorestframework.reverse import reverse
class PermissionsExampleView(View):
@ -13,11 +13,11 @@ class PermissionsExampleView(View):
return Response([
{
'name': 'Throttling Example',
'url': reverse('throttled-resource')
'url': reverse('throttled-resource', request)
},
{
'name': 'Logged in example',
'url': reverse('loggedin-resource')
'url': reverse('loggedin-resource', request)
},
])

View File

@ -1,10 +1,10 @@
from __future__ import with_statement # for python 2.5
from django.conf import settings
from django.core.urlresolvers import reverse
from djangorestframework.resources import FormResource
from djangorestframework.response import Response
from djangorestframework.renderers import BaseRenderer
from djangorestframework.reverse import reverse
from djangorestframework.views import View
from djangorestframework import status
@ -61,7 +61,7 @@ class PygmentsRoot(View):
Return a list of all currently existing snippets.
"""
unique_ids = [os.path.split(f)[1] for f in list_dir_sorted_by_ctime(HIGHLIGHTED_CODE_DIR)]
return Response([reverse('pygments-instance', args=[unique_id]) for unique_id in unique_ids])
return Response([reverse('pygments-instance', request, args=[unique_id]) for unique_id in unique_ids])
def post(self, request):
"""
@ -81,8 +81,8 @@ class PygmentsRoot(View):
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
self.headers['Location'] = reverse('pygments-instance', args=[unique_id])
return Response(status=status.HTTP_201_CREATED)
location = reverse('pygments-instance', request, args=[unique_id])
return Response(status=status.HTTP_201_CREATED, headers={'Location': location})
class PygmentsInstance(View):
@ -98,7 +98,7 @@ class PygmentsInstance(View):
"""
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
if not os.path.exists(pathname):
return Response(status.HTTP_404_NOT_FOUND)
return Response(status=status.HTTP_404_NOT_FOUND)
return Response(open(pathname, 'r').read())
def delete(self, request, unique_id):
@ -107,6 +107,7 @@ class PygmentsInstance(View):
"""
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
if not os.path.exists(pathname):
return Response(status.HTTP_404_NOT_FOUND)
return Response(os.remove(pathname))
return Response(status=status.HTTP_404_NOT_FOUND)
os.remove(pathname)
return Response()

View File

@ -1,5 +1,4 @@
from django.core.urlresolvers import reverse
from djangorestframework.reverse import reverse
from djangorestframework.views import View
from djangorestframework.response import Response
from djangorestframework import status
@ -14,9 +13,12 @@ class ExampleView(View):
def get(self, request):
"""
Handle GET requests, returning a list of URLs pointing to 3 other views.
Handle GET requests, returning a list of URLs pointing to
three other views.
"""
return Response({"Some other resources": [reverse('another-example', kwargs={'num':num}) for num in range(3)]})
urls = [reverse('another-example', request, kwargs={'num': num})
for num in range(3)]
return Response({"Some other resources": urls})
class AnotherExampleView(View):
@ -32,7 +34,7 @@ class AnotherExampleView(View):
Returns a simple string indicating which view the GET request was for.
"""
if int(num) > 2:
return Response(status.HTTP_404_NOT_FOUND)
return Response(status=status.HTTP_404_NOT_FOUND)
return Response("GET request to AnotherExampleResource %s" % num)
def post(self, request, num):
@ -41,5 +43,5 @@ class AnotherExampleView(View):
Returns a simple string indicating what content was supplied.
"""
if int(num) > 2:
return Response(status.HTTP_404_NOT_FOUND)
return Response(status=status.HTTP_404_NOT_FOUND)
return Response("POST request to AnotherExampleResource %s, with content: %s" % (num, repr(self.CONTENT)))

View File

@ -1,40 +1,67 @@
"""The root view for the examples provided with Django REST framework"""
from django.core.urlresolvers import reverse
from djangorestframework.reverse import reverse
from djangorestframework.views import View
from djangorestframework.response import Response
class Sandbox(View):
"""This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org).
"""
This is the sandbox for the examples provided with
[Django REST framework][1].
These examples are provided to help you get a better idea of some of the features of RESTful APIs created using the framework.
These examples are provided to help you get a better idea of some of the
features of RESTful APIs created using the framework.
All the example APIs allow anonymous access, and can be navigated either through the browser or from the command line...
All the example APIs allow anonymous access, and can be navigated either
through the browser or from the command line.
bash: curl -X GET http://api.django-rest-framework.org/ # (Use default renderer)
bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
For example, to get the default representation using curl:
bash: curl -X GET http://rest.ep.io/
Or, to get the plaintext documentation represention:
bash: curl -X GET http://rest.ep.io/ -H 'Accept: text/plain'
The examples provided:
1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class.
3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [RendererMixin](http://django-rest-framework.org/library/renderers.html).
1. A basic example using the [Resource][2] class.
2. A basic example using the [ModelResource][3] class.
3. An basic example using Django 1.3's [class based views][4] and
djangorestframework's [RendererMixin][5].
4. A generic object store API.
5. A code highlighting API.
6. A blog posts and comments API.
7. A basic example using permissions.
8. A basic example using enhanced request.
Please feel free to browse, create, edit and delete the resources in these examples."""
Please feel free to browse, create, edit and delete the resources in
these examples.
[1]: http://django-rest-framework.org
[2]: http://django-rest-framework.org/library/resource.html
[3]: http://django-rest-framework.org/library/modelresource.html
[4]: http://docs.djangoproject.com/en/dev/topics/class-based-views/
[5]: http://django-rest-framework.org/library/renderers.html
"""
def get(self, request):
return Response([{'name': 'Simple Resource example', 'url': reverse('example-resource')},
{'name': 'Simple ModelResource example', 'url': reverse('model-resource-root')},
{'name': 'Simple Mixin-only example', 'url': reverse('mixin-view')},
{'name': 'Object store API', 'url': reverse('object-store-root')},
{'name': 'Code highlighting API', 'url': reverse('pygments-root')},
{'name': 'Blog posts API', 'url': reverse('blog-posts-root')},
{'name': 'Permissions example', 'url': reverse('permissions-example')},
{'name': 'Simple request mixin example', 'url': reverse('request-example')}
])
return Response([
{'name': 'Simple Resource example',
'url': reverse('example-resource', request)},
{'name': 'Simple ModelResource example',
'url': reverse('model-resource-root', request)},
{'name': 'Simple Mixin-only example',
'url': reverse('mixin-view', request)},
{'name': 'Object store API'
'url': reverse('object-store-root', request)},
{'name': 'Code highlighting API',
'url': reverse('pygments-root', request)},
{'name': 'Blog posts API',
'url': reverse('blog-posts-root', request)},
{'name': 'Permissions example',
'url': reverse('permissions-example', request)},
{'name': 'Simple request mixin example',
'url': reverse('request-example', request)}
])