mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-04-28 21:13:43 +03:00
merged + fixed broken test
This commit is contained in:
commit
9da1ae81dc
3
AUTHORS
3
AUTHORS
|
@ -30,6 +30,9 @@ Chris Pickett <bunchesofdonald>
|
||||||
Ben Timby <btimby>
|
Ben Timby <btimby>
|
||||||
Michele Lazzeri <michelelazzeri-nextage>
|
Michele Lazzeri <michelelazzeri-nextage>
|
||||||
Camille Harang <mammique>
|
Camille Harang <mammique>
|
||||||
|
Paul Oswald <poswald>
|
||||||
|
Sean C. Farley <scfarley>
|
||||||
|
Daniel Izquierdo <izquierdo>
|
||||||
|
|
||||||
THANKS TO:
|
THANKS TO:
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
development
|
0.3.3
|
||||||
-----------
|
-----
|
||||||
|
|
||||||
* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
|
* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
|
||||||
* Use `staticfiles` for css files.
|
* Use `staticfiles` for css files.
|
||||||
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
|
- 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.
|
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
|
||||||
- Too magical. Explict is better than implicit.
|
- Too magical. Explict is better than implicit.
|
||||||
* Saner template variable autoescaping.
|
* Saner template variable autoescaping.
|
||||||
* Tider setup.py
|
* Tider setup.py
|
||||||
|
* Updated for URLObject 2.0
|
||||||
* Bugfixes:
|
* Bugfixes:
|
||||||
- Bug with PerUserThrottling when user contains unicode chars.
|
- Bug with PerUserThrottling when user contains unicode chars.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = '0.3.3-dev'
|
__version__ = '0.3.3'
|
||||||
|
|
||||||
VERSION = __version__ # synonym
|
VERSION = __version__ # synonym
|
||||||
|
|
|
@ -457,3 +457,11 @@ except ImportError: # python < 2.7
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
unittest.skip = skip
|
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)
|
||||||
|
|
|
@ -13,7 +13,6 @@ from djangorestframework.renderers import BaseRenderer
|
||||||
from djangorestframework.resources import Resource, FormResource, ModelResource
|
from djangorestframework.resources import Resource, FormResource, ModelResource
|
||||||
from djangorestframework.response import Response, ImmediateResponse
|
from djangorestframework.response import Response, ImmediateResponse
|
||||||
from djangorestframework.request import Request
|
from djangorestframework.request import Request
|
||||||
from djangorestframework.utils import as_tuple, allowed_methods
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
@ -498,12 +497,12 @@ class PaginatorMixin(object):
|
||||||
"""
|
"""
|
||||||
Constructs a url used for getting the next/previous urls
|
Constructs a url used for getting the next/previous urls
|
||||||
"""
|
"""
|
||||||
url = URLObject.parse(self.request.get_full_path())
|
url = URLObject(self.request.get_full_path())
|
||||||
url = url.set_query_param('page', page_number)
|
url = url.set_query_param('page', str(page_number))
|
||||||
|
|
||||||
limit = self.get_limit()
|
limit = self.get_limit()
|
||||||
if limit != self.limit:
|
if limit != self.limit:
|
||||||
url = url.add_query_param('limit', limit)
|
url = url.set_query_param('limit', str(limit))
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
|
@ -379,7 +379,7 @@ class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
|
||||||
|
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
format = 'html'
|
format = 'html'
|
||||||
template = 'renderer.html'
|
template = 'djangorestframework/api.html'
|
||||||
|
|
||||||
|
|
||||||
class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
|
class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
|
||||||
|
@ -391,7 +391,7 @@ class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
|
||||||
|
|
||||||
media_type = 'application/xhtml+xml'
|
media_type = 'application/xhtml+xml'
|
||||||
format = 'xhtml'
|
format = 'xhtml'
|
||||||
template = 'renderer.html'
|
template = 'djangorestframework/api.html'
|
||||||
|
|
||||||
|
|
||||||
class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
|
class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
|
||||||
|
@ -403,7 +403,7 @@ class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
|
||||||
|
|
||||||
media_type = 'text/plain'
|
media_type = 'text/plain'
|
||||||
format = 'txt'
|
format = 'txt'
|
||||||
template = 'renderer.txt'
|
template = 'djangorestframework/api.txt'
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_RENDERERS = (
|
DEFAULT_RENDERERS = (
|
||||||
|
|
|
@ -9,11 +9,9 @@ The wrapped request then offers a richer API, in particular :
|
||||||
- form overloading of HTTP method, content type and content
|
- form overloading of HTTP method, content type and content
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.http import HttpRequest
|
|
||||||
|
|
||||||
from djangorestframework.response import ImmediateResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
from djangorestframework import status
|
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 djangorestframework.utils import as_tuple
|
||||||
|
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from django import forms
|
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 django.db import models
|
||||||
|
|
||||||
from djangorestframework.response import ImmediateResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
from djangorestframework.serializer import Serializer, _SkipField
|
from djangorestframework.serializer import Serializer, _SkipField
|
||||||
from djangorestframework.utils import as_tuple
|
from djangorestframework.utils import as_tuple, reverse
|
||||||
|
|
||||||
|
|
||||||
class BaseResource(Serializer):
|
class BaseResource(Serializer):
|
||||||
|
@ -354,7 +355,7 @@ class ModelResource(FormResource):
|
||||||
instance_attrs[param] = attr
|
instance_attrs[param] = attr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return reverse(self.view_callable[0], kwargs=instance_attrs)
|
return reverse(self.view_callable[0], self.view.request, kwargs=instance_attrs)
|
||||||
except NoReverseMatch:
|
except NoReverseMatch:
|
||||||
pass
|
pass
|
||||||
raise _SkipField
|
raise _SkipField
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Response(SimpleTemplateResponse):
|
||||||
An HttpResponse that may include content that hasn't yet been serialized.
|
An HttpResponse that may include content that hasn't yet been serialized.
|
||||||
|
|
||||||
Kwargs:
|
Kwargs:
|
||||||
- content(object). The raw content, not yet serialized. This must be simple Python \
|
- content(object). The raw content, not yet serialized. This must be simple Python
|
||||||
data that renderers can handle (e.g.: `dict`, `str`, ...)
|
data that renderers can handle (e.g.: `dict`, `str`, ...)
|
||||||
- renderers(list/tuple). The renderers to use for rendering the response content.
|
- renderers(list/tuple). The renderers to use for rendering the response content.
|
||||||
"""
|
"""
|
||||||
|
@ -40,7 +40,7 @@ class Response(SimpleTemplateResponse):
|
||||||
_ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
|
_ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
|
||||||
_IGNORE_IE_ACCEPT_HEADER = True
|
_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,
|
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
|
||||||
# which we don't need
|
# which we don't need
|
||||||
super(Response, self).__init__(None, status=status)
|
super(Response, self).__init__(None, status=status)
|
||||||
|
@ -50,6 +50,7 @@ class Response(SimpleTemplateResponse):
|
||||||
self.raw_content = content
|
self.raw_content = content
|
||||||
self.has_content_body = content is not None
|
self.has_content_body = content is not None
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.headers = headers and headers[:] or []
|
||||||
if renderers is not None:
|
if renderers is not None:
|
||||||
self.renderers = renderers
|
self.renderers = renderers
|
||||||
|
|
||||||
|
|
23
djangorestframework/reverse.py
Normal file
23
djangorestframework/reverse.py
Normal 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)
|
|
@ -146,7 +146,7 @@ class Serializer(object):
|
||||||
# then the second element of the tuple is the fields to
|
# then the second element of the tuple is the fields to
|
||||||
# set on the related serializer
|
# set on the related serializer
|
||||||
if isinstance(info, (list, tuple)):
|
if isinstance(info, (list, tuple)):
|
||||||
class OnTheFlySerializer(Serializer):
|
class OnTheFlySerializer(self.__class__):
|
||||||
fields = info
|
fields = info
|
||||||
return OnTheFlySerializer
|
return OnTheFlySerializer
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ tfoot td {
|
||||||
color: #666;
|
color: #666;
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
font-size: 11px;
|
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-left: 1px solid #ddd;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
@ -317,11 +317,11 @@ table thead th.sorted a {
|
||||||
}
|
}
|
||||||
|
|
||||||
table thead th.ascending 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 {
|
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 */
|
/* ORDERABLE TABLES */
|
||||||
|
@ -332,7 +332,7 @@ table.orderable tbody tr td:hover {
|
||||||
|
|
||||||
table.orderable tbody tr td:first-child {
|
table.orderable tbody tr td:first-child {
|
||||||
padding-left: 14px;
|
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;
|
background-repeat: repeat-y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ input[type=text], input[type=password], textarea, select, .vTextField {
|
||||||
/* FORM BUTTONS */
|
/* FORM BUTTONS */
|
||||||
|
|
||||||
.button, input[type=submit], input[type=button], .submit-row input {
|
.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;
|
padding: 3px 5px;
|
||||||
color: black;
|
color: black;
|
||||||
border: 1px solid #bbb;
|
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 {
|
.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;
|
background-position: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
|
.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;
|
background-position: bottom;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.default, input[type=submit].default, .submit-row input.default {
|
.button.default, input[type=submit].default, .submit-row input.default {
|
||||||
border: 2px solid #5b80b2;
|
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;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.default:active, input[type=submit].default:active {
|
.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;
|
background-position: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button[disabled].default, input[type=submit][disabled].default, input[type=button][disabled].default {
|
.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;
|
background-position: bottom;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
@ -431,7 +431,7 @@ input[type=text], input[type=password], textarea, select, .vTextField {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: bold;
|
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;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,15 +453,15 @@ ul.messagelist li {
|
||||||
margin: 0 0 3px 0;
|
margin: 0 0 3px 0;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
color: #666;
|
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{
|
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{
|
ul.messagelist li.error{
|
||||||
background-image: url(../img/admin/icon_error.gif);
|
background-image: url(../../admin/img/admin/icon_error.gif);
|
||||||
}
|
}
|
||||||
|
|
||||||
.errornote {
|
.errornote {
|
||||||
|
@ -471,7 +471,7 @@ ul.messagelist li.error{
|
||||||
margin: 0 0 3px 0;
|
margin: 0 0 3px 0;
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
color: 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 {
|
ul.errorlist {
|
||||||
|
@ -486,7 +486,7 @@ ul.errorlist {
|
||||||
margin: 0 0 3px 0;
|
margin: 0 0 3px 0;
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
color: white;
|
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 {
|
.errorlist li a {
|
||||||
|
@ -522,7 +522,7 @@ div.system-message p.system-message-title {
|
||||||
padding: 4px 5px 4px 25px;
|
padding: 4px 5px 4px 25px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
@ -533,7 +533,7 @@ div.system-message p.system-message-title {
|
||||||
/* BREADCRUMBS */
|
/* BREADCRUMBS */
|
||||||
|
|
||||||
div.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;
|
padding: 2px 8px 3px 8px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
@ -546,17 +546,17 @@ div.breadcrumbs {
|
||||||
|
|
||||||
.addlink {
|
.addlink {
|
||||||
padding-left: 12px;
|
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 {
|
.changelink {
|
||||||
padding-left: 12px;
|
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 {
|
.deletelink {
|
||||||
padding-left: 12px;
|
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 {
|
a.deletelink:link, a.deletelink:visited {
|
||||||
|
@ -591,14 +591,14 @@ a.deletelink:hover {
|
||||||
.object-tools li {
|
.object-tools li {
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
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;
|
padding: 0 0 0 8px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.object-tools li:hover {
|
.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 {
|
.object-tools a:link, .object-tools a:visited {
|
||||||
|
@ -607,29 +607,29 @@ a.deletelink:hover {
|
||||||
color: white;
|
color: white;
|
||||||
padding: .1em 14px .1em 8px;
|
padding: .1em 14px .1em 8px;
|
||||||
height: 14px;
|
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 {
|
.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 {
|
.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;
|
padding-right: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.object-tools a.viewsitelink:hover, .object-tools a.golink:hover {
|
.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 {
|
.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;
|
padding-right: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.object-tools a.addlink:hover {
|
.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 */
|
/* OBJECT HISTORY */
|
||||||
|
@ -764,7 +764,7 @@ table#change-history tbody th {
|
||||||
}
|
}
|
||||||
|
|
||||||
#content-related .module h2 {
|
#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;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,7 +910,7 @@ fieldset.collapsed h2, fieldset.collapsed {
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset.collapsed h2 {
|
fieldset.collapsed h2 {
|
||||||
background-image: url(../img/admin/nav-bg.gif);
|
background-image: url(../../admin/img/admin/nav-bg.gif);
|
||||||
background-position: bottom left;
|
background-position: bottom left;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
@ -931,7 +931,7 @@ fieldset.monospace textarea {
|
||||||
.submit-row {
|
.submit-row {
|
||||||
padding: 5px 7px;
|
padding: 5px 7px;
|
||||||
text-align: right;
|
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;
|
border: 1px solid #ccc;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -950,7 +950,7 @@ fieldset.monospace textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-row .deletelink {
|
.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;
|
padding-left: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,7 +1017,7 @@ fieldset.monospace textarea {
|
||||||
color: #666;
|
color: #666;
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
font-size: 11px;
|
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;
|
border-bottom: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1102,7 +1102,7 @@ fieldset.monospace textarea {
|
||||||
color: #666;
|
color: #666;
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
border-bottom: 1px solid #ddd;
|
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 {
|
.inline-group .tabular tr.add-row td {
|
||||||
|
@ -1113,7 +1113,7 @@ fieldset.monospace textarea {
|
||||||
.inline-group ul.tools a.add,
|
.inline-group ul.tools a.add,
|
||||||
.inline-group div.add-row a,
|
.inline-group div.add-row a,
|
||||||
.inline-group .tabular tr.add-row td 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;
|
padding-left: 14px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
outline: 0; /* Remove dotted border around link */
|
outline: 0; /* Remove dotted border around link */
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,2 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Disallow: /
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "djangorestframework/base.html" %}
|
||||||
|
|
||||||
|
{# Override this template in your own templates directory to customize #}
|
|
@ -6,27 +6,35 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<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'/>
|
||||||
<title>Django REST framework - {{ name }}</title>
|
{% 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>
|
</head>
|
||||||
<body>
|
<body class="{% block bodyclass %}{% endblock %}">
|
||||||
<div id="container">
|
<div id="container">
|
||||||
|
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="branding">
|
<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>
|
||||||
<div id="user-tools">
|
<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 %}
|
{% 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>
|
</div>
|
||||||
|
{% block nav-global %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
|
{% block breadcrumbs %}
|
||||||
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
||||||
<a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}›{% endif %}
|
<a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}›{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
<div id="content" class="{% block coltype %}colM{% endblock %}">
|
<div id="content" class="{% block coltype %}colM{% endblock %}">
|
||||||
|
|
||||||
{% if 'OPTIONS' in allowed_methods %}
|
{% if 'OPTIONS' in allowed_methods %}
|
||||||
|
@ -123,7 +131,12 @@
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- END content-main -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- END Content -->
|
||||||
|
|
||||||
|
{% block footer %}<div id="footer"></div>{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -2,7 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body class="login">
|
<body class="login">
|
|
@ -4,8 +4,7 @@ register = Library()
|
||||||
|
|
||||||
|
|
||||||
def add_query_param(url, param):
|
def add_query_param(url, param):
|
||||||
(key, sep, val) = param.partition('=')
|
return unicode(URLObject(url).with_query(param))
|
||||||
return unicode(URLObject.parse(url) & (key, val))
|
|
||||||
|
|
||||||
|
|
||||||
register.filter('add_query_param', add_query_param)
|
register.filter('add_query_param', add_query_param)
|
||||||
|
|
|
@ -1,29 +1,34 @@
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
|
|
||||||
|
from djangorestframework.renderers import JSONRenderer
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
|
||||||
|
|
||||||
class MockView(View):
|
class MyView(View):
|
||||||
"""Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified"""
|
"""
|
||||||
permissions = ()
|
Mock resource which simply returns a URL, so that we can ensure
|
||||||
|
that reversed URLs are fully qualified.
|
||||||
|
"""
|
||||||
|
renderers = (JSONRenderer, )
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return Response(reverse('another'))
|
return Response(reverse('myview', request))
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', MockView.as_view()),
|
url(r'^myview$', MyView.as_view(), name='myview'),
|
||||||
url(r'^another$', MockView.as_view(), name='another'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReverseTests(TestCase):
|
class ReverseTests(TestCase):
|
||||||
"""Tests for """
|
"""
|
||||||
|
Tests for fully qualifed URLs when using `reverse`.
|
||||||
|
"""
|
||||||
urls = 'djangorestframework.tests.reverse'
|
urls = 'djangorestframework.tests.reverse'
|
||||||
|
|
||||||
def test_reversed_urls_are_fully_qualified(self):
|
def test_reversed_urls_are_fully_qualified(self):
|
||||||
response = self.client.get('/')
|
response = self.client.get('/myview')
|
||||||
self.assertEqual(json.loads(response.content), 'http://testserver/another')
|
self.assertEqual(json.loads(response.content), 'http://testserver/myview')
|
||||||
|
|
|
@ -46,8 +46,6 @@ class MockResource(ModelResource):
|
||||||
fields = ('foo', 'bar', 'baz')
|
fields = ('foo', 'bar', 'baz')
|
||||||
|
|
||||||
urlpatterns = patterns('djangorestframework.utils.staticviews',
|
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/login$', 'api_login'),
|
||||||
url(r'^accounts/logout$', 'api_logout'),
|
url(r'^accounts/logout$', 'api_logout'),
|
||||||
url(r'^mock/$', MockView.as_view()),
|
url(r'^mock/$', MockView.as_view()),
|
||||||
|
@ -123,18 +121,6 @@ class ExtraViewsTests(TestCase):
|
||||||
"""Test the extra views djangorestframework provides"""
|
"""Test the extra views djangorestframework provides"""
|
||||||
urls = 'djangorestframework.tests.views'
|
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):
|
def test_login_view(self):
|
||||||
"""Ensure the login view exists"""
|
"""Ensure the login view exists"""
|
||||||
response = self.client.get('/accounts/login')
|
response = self.client.get('/accounts/login')
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
from django.conf.urls.defaults import patterns
|
from django.conf.urls.defaults import patterns
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
urlpatterns = patterns('djangorestframework.utils.staticviews',
|
urlpatterns = patterns('djangorestframework.utils.staticviews',
|
||||||
(r'robots.txt', 'deny_robots'),
|
|
||||||
(r'^accounts/login/$', 'api_login'),
|
(r'^accounts/login/$', 'api_login'),
|
||||||
(r'^accounts/logout/$', 'api_logout'),
|
(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'),
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import django
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
from django.utils.xmlutils import SimplerXMLGenerator
|
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 django.conf import settings
|
||||||
|
|
||||||
from djangorestframework.compat import StringIO
|
from djangorestframework.compat import StringIO
|
||||||
|
@ -180,3 +181,21 @@ class XMLRenderer():
|
||||||
|
|
||||||
def dict2xml(input):
|
def dict2xml(input):
|
||||||
return XMLRenderer().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))
|
||||||
|
|
|
@ -6,22 +6,13 @@ from django.template import RequestContext
|
||||||
import base64
|
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
|
# BLERGH
|
||||||
# Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS
|
# 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
|
# 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
|
# be making settings changes in order to accomodate django-rest-framework
|
||||||
@csrf_protect
|
@csrf_protect
|
||||||
@never_cache
|
@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,
|
redirect_field_name=REDIRECT_FIELD_NAME,
|
||||||
authentication_form=AuthenticationForm):
|
authentication_form=AuthenticationForm):
|
||||||
"""Displays the login form and handles the login action."""
|
"""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))
|
}, 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)
|
return logout(request, next_page, template_name, redirect_field_name)
|
||||||
|
|
|
@ -7,13 +7,12 @@ By setting or modifying class attributes on your view, you change it's predefine
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from django.core.urlresolvers import set_script_prefix, get_script_prefix
|
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.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from djangorestframework.compat import View as DjangoView, apply_markdown
|
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.mixins import *
|
||||||
from djangorestframework.utils import allowed_methods
|
from djangorestframework.utils import allowed_methods
|
||||||
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
|
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
|
||||||
|
@ -163,6 +162,9 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||||
return description
|
return description
|
||||||
|
|
||||||
def markup_description(self, description):
|
def markup_description(self, description):
|
||||||
|
"""
|
||||||
|
Apply HTML markup to the description of this view.
|
||||||
|
"""
|
||||||
if apply_markdown:
|
if apply_markdown:
|
||||||
description = apply_markdown(description)
|
description = apply_markdown(description)
|
||||||
else:
|
else:
|
||||||
|
@ -171,11 +173,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||||
|
|
||||||
def http_method_not_allowed(self, request, *args, **kwargs):
|
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(
|
content = {
|
||||||
{'detail': 'Method \'%s\' not allowed on this resource.' % request.method},
|
'detail': "Method '%s' not allowed on this resource." % request.method
|
||||||
status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
}
|
||||||
|
raise ImmediateResponse(content, status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
def initial(self, request, *args, **kargs):
|
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
|
Required if you want to do things like set `request.upload_handlers` before
|
||||||
the authentication and dispatch handling is run.
|
the authentication and dispatch handling is run.
|
||||||
"""
|
"""
|
||||||
# Calls to 'reverse' will not be fully qualified unless we set the
|
pass
|
||||||
# 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
|
|
||||||
|
|
||||||
def final(self, request, response, *args, **kargs):
|
def final(self, request, response, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
Returns an `HttpResponse`. This method is a hook for any code that needs to run
|
Returns an `HttpResponse`. This method is a hook for any code that needs to run
|
||||||
after everything else in the view.
|
after everything else in the view.
|
||||||
"""
|
"""
|
||||||
# Restore script_prefix.
|
|
||||||
set_script_prefix(self.orig_prefix)
|
|
||||||
|
|
||||||
# Always add these headers.
|
# Always add these headers.
|
||||||
response['Allow'] = ', '.join(allowed_methods(self))
|
response['Allow'] = ', '.join(allowed_methods(self))
|
||||||
# sample to allow caching using Vary http header
|
# 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.
|
# all other authentication is CSRF exempt.
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.request = request
|
self.request = self.create_request(request)
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get a custom request, built form the original request instance
|
self.initial(request, *args, **kwargs)
|
||||||
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)
|
|
||||||
|
|
||||||
# Authenticate and check request has the relevant permissions
|
# Authenticate and check request has the relevant permissions
|
||||||
self._check_permissions()
|
self._check_permissions()
|
||||||
|
|
39
docs/howto/reverse.rst
Normal file
39
docs/howto/reverse.rst
Normal 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
|
|
@ -3,52 +3,70 @@
|
||||||
Setup
|
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,
|
Django REST framework requires `django.contrib.staticfiles`_ to serve it's css.
|
||||||
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 you're using Django 1.2 you'll need to use the seperate
|
||||||
`if using the Django admin <https://docs.djangoproject.com/en/dev/howto/deployment/modpython/#serving-the-admin-files>`_.
|
`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
|
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
|
If markdown is installed your :class:`.Resource` descriptions can include
|
||||||
<http://daringfireball.net/projects/markdown/syntax>`_ which will be rendered by the HTML documenting renderer.
|
`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
|
from django.conf.urls.defaults import patterns
|
||||||
|
|
||||||
urlpatterns = patterns('djangorestframework.views',
|
urlpatterns = patterns('djangorestframework.views',
|
||||||
(r'robots.txt', 'deny_robots'),
|
|
||||||
(r'favicon.ico', 'favicon'),
|
|
||||||
# Add your resources here
|
# Add your resources here
|
||||||
(r'^accounts/login/$', 'api_login'),
|
(r'^accounts/login/$', 'api_login'),
|
||||||
(r'^accounts/logout/$', 'api_logout'),
|
(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
|
|
@ -40,8 +40,11 @@ Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* Python (2.5, 2.6, 2.7 supported)
|
* 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
|
Installation
|
||||||
------------
|
------------
|
||||||
|
@ -54,8 +57,6 @@ Or get the latest development version using git::
|
||||||
|
|
||||||
git clone git@github.com:tomchristie/django-rest-framework.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
|
Setup
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -114,3 +115,8 @@ Indices and tables
|
||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
* :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
5
docs/library/reverse.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
:mod:`reverse`
|
||||||
|
================
|
||||||
|
|
||||||
|
.. automodule:: reverse
|
||||||
|
:members:
|
5
docs/library/utils.rst
Normal file
5
docs/library/utils.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
:mod:`utils`
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. automodule:: utils
|
||||||
|
:members:
|
|
@ -1,5 +1,5 @@
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from djangorestframework.resources import ModelResource
|
from djangorestframework.resources import ModelResource
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
from blogpost.models import BlogPost, Comment
|
from blogpost.models import BlogPost, Comment
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class BlogPostResource(ModelResource):
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
|
||||||
def comments(self, instance):
|
def comments(self, instance):
|
||||||
return reverse('comments', kwargs={'blogpost': instance.key})
|
return reverse('comments', request, kwargs={'blogpost': instance.key})
|
||||||
|
|
||||||
|
|
||||||
class CommentResource(ModelResource):
|
class CommentResource(ModelResource):
|
||||||
|
@ -24,4 +24,4 @@ class CommentResource(ModelResource):
|
||||||
ordering = ('-created',)
|
ordering = ('-created',)
|
||||||
|
|
||||||
def blogpost(self, instance):
|
def blogpost(self, instance):
|
||||||
return reverse('blog-post', kwargs={'key': instance.blogpost.key})
|
return reverse('blog-post', request, kwargs={'key': instance.blogpost.key})
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
"""Test a range of REST API usage of the example application.
|
"""Test a range of REST API usage of the example application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
|
|
||||||
from djangorestframework.compat import RequestFactory
|
from djangorestframework.compat import RequestFactory
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
from djangorestframework.views import InstanceModelView, ListOrCreateModelView
|
from djangorestframework.views import InstanceModelView, ListOrCreateModelView
|
||||||
|
|
||||||
from blogpost import models, urls
|
from blogpost import models, urls
|
||||||
|
|
|
@ -2,9 +2,9 @@ from djangorestframework.compat import View # Use Django 1.3's django.views.gen
|
||||||
from djangorestframework.mixins import ResponseMixin
|
from djangorestframework.mixins import ResponseMixin
|
||||||
from djangorestframework.renderers import DEFAULT_RENDERERS
|
from djangorestframework.renderers import DEFAULT_RENDERERS
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
|
|
||||||
from django.conf.urls.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
|
|
||||||
class ExampleView(ResponseMixin, View):
|
class ExampleView(ResponseMixin, View):
|
||||||
|
@ -14,7 +14,7 @@ class ExampleView(ResponseMixin, View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
response = Response({'description': 'Some example content',
|
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)
|
self.response = self.prepare_response(response)
|
||||||
return self.response
|
return self.response
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
from djangorestframework import status
|
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('.')]
|
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],
|
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)]
|
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):
|
def post(self, request):
|
||||||
"""
|
"""
|
||||||
|
@ -51,8 +51,8 @@ class ObjectStoreRoot(View):
|
||||||
pathname = os.path.join(OBJECT_STORE_DIR, key)
|
pathname = os.path.join(OBJECT_STORE_DIR, key)
|
||||||
pickle.dump(self.CONTENT, open(pathname, 'wb'))
|
pickle.dump(self.CONTENT, open(pathname, 'wb'))
|
||||||
remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES)
|
remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES)
|
||||||
self.headers['Location'] = reverse('stored-object', kwargs={'key':key})
|
url = reverse('stored-object', request, kwargs={'key':key})
|
||||||
return Response(self.CONTENT, status=status.HTTP_201_CREATED)
|
return Response(self.CONTENT, status.HTTP_201_CREATED, {'Location': url})
|
||||||
|
|
||||||
|
|
||||||
class StoredObject(View):
|
class StoredObject(View):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
|
from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
|
||||||
from django.core.urlresolvers import reverse
|
from djangorestframework.reverse import reverse
|
||||||
|
|
||||||
|
|
||||||
class PermissionsExampleView(View):
|
class PermissionsExampleView(View):
|
||||||
|
@ -13,11 +13,11 @@ class PermissionsExampleView(View):
|
||||||
return Response([
|
return Response([
|
||||||
{
|
{
|
||||||
'name': 'Throttling Example',
|
'name': 'Throttling Example',
|
||||||
'url': reverse('throttled-resource')
|
'url': reverse('throttled-resource', request)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Logged in example',
|
'name': 'Logged in example',
|
||||||
'url': reverse('loggedin-resource')
|
'url': reverse('loggedin-resource', request)
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from __future__ import with_statement # for python 2.5
|
from __future__ import with_statement # for python 2.5
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
from djangorestframework.resources import FormResource
|
from djangorestframework.resources import FormResource
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
from djangorestframework.renderers import BaseRenderer
|
from djangorestframework.renderers import BaseRenderer
|
||||||
|
from djangorestframework.reverse import reverse
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class PygmentsRoot(View):
|
||||||
Return a list of all currently existing snippets.
|
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)]
|
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):
|
def post(self, request):
|
||||||
"""
|
"""
|
||||||
|
@ -81,8 +81,8 @@ class PygmentsRoot(View):
|
||||||
|
|
||||||
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
|
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
|
||||||
|
|
||||||
self.headers['Location'] = reverse('pygments-instance', args=[unique_id])
|
location = reverse('pygments-instance', request, args=[unique_id])
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
return Response(status=status.HTTP_201_CREATED, headers={'Location': location})
|
||||||
|
|
||||||
|
|
||||||
class PygmentsInstance(View):
|
class PygmentsInstance(View):
|
||||||
|
@ -98,7 +98,7 @@ class PygmentsInstance(View):
|
||||||
"""
|
"""
|
||||||
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
|
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
|
||||||
if not os.path.exists(pathname):
|
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())
|
return Response(open(pathname, 'r').read())
|
||||||
|
|
||||||
def delete(self, request, unique_id):
|
def delete(self, request, unique_id):
|
||||||
|
@ -107,6 +107,7 @@ class PygmentsInstance(View):
|
||||||
"""
|
"""
|
||||||
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
|
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
|
||||||
if not os.path.exists(pathname):
|
if not os.path.exists(pathname):
|
||||||
return Response(status.HTTP_404_NOT_FOUND)
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
return Response(os.remove(pathname))
|
os.remove(pathname)
|
||||||
|
return Response()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.core.urlresolvers import reverse
|
from djangorestframework.reverse import reverse
|
||||||
|
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
|
@ -14,9 +13,12 @@ class ExampleView(View):
|
||||||
|
|
||||||
def get(self, request):
|
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):
|
class AnotherExampleView(View):
|
||||||
|
@ -32,7 +34,7 @@ class AnotherExampleView(View):
|
||||||
Returns a simple string indicating which view the GET request was for.
|
Returns a simple string indicating which view the GET request was for.
|
||||||
"""
|
"""
|
||||||
if int(num) > 2:
|
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)
|
return Response("GET request to AnotherExampleResource %s" % num)
|
||||||
|
|
||||||
def post(self, request, num):
|
def post(self, request, num):
|
||||||
|
@ -41,5 +43,5 @@ class AnotherExampleView(View):
|
||||||
Returns a simple string indicating what content was supplied.
|
Returns a simple string indicating what content was supplied.
|
||||||
"""
|
"""
|
||||||
if int(num) > 2:
|
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)))
|
return Response("POST request to AnotherExampleResource %s, with content: %s" % (num, repr(self.CONTENT)))
|
||||||
|
|
|
@ -1,40 +1,67 @@
|
||||||
"""The root view for the examples provided with Django REST framework"""
|
"""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.views import View
|
||||||
from djangorestframework.response import Response
|
from djangorestframework.response import Response
|
||||||
|
|
||||||
|
|
||||||
class Sandbox(View):
|
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)
|
For example, to get the default representation using curl:
|
||||||
bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
|
|
||||||
|
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:
|
The examples provided:
|
||||||
|
|
||||||
1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
|
1. A basic example using the [Resource][2] class.
|
||||||
2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class.
|
2. A basic example using the [ModelResource][3] 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).
|
3. An basic example using Django 1.3's [class based views][4] and
|
||||||
|
djangorestframework's [RendererMixin][5].
|
||||||
4. A generic object store API.
|
4. A generic object store API.
|
||||||
5. A code highlighting API.
|
5. A code highlighting API.
|
||||||
6. A blog posts and comments API.
|
6. A blog posts and comments API.
|
||||||
7. A basic example using permissions.
|
7. A basic example using permissions.
|
||||||
8. A basic example using enhanced request.
|
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):
|
def get(self, request):
|
||||||
return Response([{'name': 'Simple Resource example', 'url': reverse('example-resource')},
|
return Response([
|
||||||
{'name': 'Simple ModelResource example', 'url': reverse('model-resource-root')},
|
{'name': 'Simple Resource example',
|
||||||
{'name': 'Simple Mixin-only example', 'url': reverse('mixin-view')},
|
'url': reverse('example-resource', request)},
|
||||||
{'name': 'Object store API', 'url': reverse('object-store-root')},
|
{'name': 'Simple ModelResource example',
|
||||||
{'name': 'Code highlighting API', 'url': reverse('pygments-root')},
|
'url': reverse('model-resource-root', request)},
|
||||||
{'name': 'Blog posts API', 'url': reverse('blog-posts-root')},
|
{'name': 'Simple Mixin-only example',
|
||||||
{'name': 'Permissions example', 'url': reverse('permissions-example')},
|
'url': reverse('mixin-view', request)},
|
||||||
{'name': 'Simple request mixin example', 'url': reverse('request-example')}
|
{'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)}
|
||||||
|
])
|
||||||
|
|
Loading…
Reference in New Issue
Block a user