Merging master into develop

This commit is contained in:
Tom Christie 2012-02-21 20:12:14 +00:00
parent 21fcd3a906
commit af9e4f69d7
29 changed files with 244 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
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.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 +354,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

View File

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

View File

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

View File

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

View File

@ -7,26 +7,34 @@
<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 %}djangorestframework/css/style.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 %}&rsaquo;{% endif %} <a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}&rsaquo;{% 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>

View File

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

View File

@ -1,8 +1,8 @@
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.utils import reverse
from djangorestframework.views import View from djangorestframework.views import View
from djangorestframework.response import Response from djangorestframework.response import Response
@ -12,7 +12,7 @@ class MockView(View):
permissions = () permissions = ()
def get(self, request): def get(self, request):
return Response(reverse('another')) return Response(reverse('another', request))
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', MockView.as_view()), url(r'^$', MockView.as_view()),

View File

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

View File

@ -12,7 +12,7 @@ import base64
# 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."""
@ -57,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)

View File

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

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

@ -0,0 +1,47 @@
Returning URIs from your Web APIs
=================================
"The central feature that distinguishes the REST architectural style from
other network-based styles is its emphasis on a uniform interface between
components."
-- Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures
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:`~utils.reverse` function has the same behavior as :py:func:`django.core.urlresolvers.reverse` [1]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port::
from djangorestframework.utils 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:`~utils.reverse_lazy` function has the same behavior as :py:func:`django.core.urlresolvers.reverse_lazy` [2]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port.
.. rubric:: Footnotes
.. [1] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
.. [2] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy

View File

@ -3,45 +3,58 @@
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.
login/logout YAML
--------------------------------- ----
Django REST framework comes with a few views that can be useful including an api YAML support is optional, and requires `PyYAML`_.
login and logout views::
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
@ -51,3 +64,9 @@ login and logout views::
(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

View File

@ -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/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.resources import ModelResource
from djangorestframework.utils 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})

View File

@ -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.utils import reverse
from djangorestframework.views import InstanceModelView, ListOrCreateModelView from djangorestframework.views import InstanceModelView, ListOrCreateModelView
from blogpost import models, urls 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.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.utils 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

View File

@ -1,6 +1,6 @@
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from djangorestframework.utils 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):

View File

@ -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.utils 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)
}, },
]) ])

View File

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

View File

@ -1,5 +1,4 @@
from django.core.urlresolvers import reverse from djangorestframework.utils 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)))

View File

@ -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.utils 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)}
])