Commit af9e4f69 by Tom Christie

Merging master into develop

parent 21fcd3a9
...@@ -31,6 +31,8 @@ Ben Timby <btimby> ...@@ -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:
......
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.
......
__version__ = '0.3.3-dev' __version__ = '0.3.3'
VERSION = __version__ # synonym VERSION = __version__ # synonym
...@@ -497,12 +497,12 @@ class PaginatorMixin(object): ...@@ -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
......
...@@ -379,7 +379,7 @@ class DocumentingHTMLRenderer(DocumentingTemplateRenderer): ...@@ -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): ...@@ -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): ...@@ -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 = (
......
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): ...@@ -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
......
...@@ -40,7 +40,7 @@ class Response(SimpleTemplateResponse): ...@@ -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): ...@@ -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
......
...@@ -146,7 +146,7 @@ class Serializer(object): ...@@ -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
......
{% extends "djangorestframework/base.html" %}
{# Override this template in your own templates directory to customize #}
\ No newline at end of file
...@@ -7,26 +7,34 @@ ...@@ -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 @@ ...@@ -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>
...@@ -4,8 +4,7 @@ register = Library() ...@@ -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)
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): ...@@ -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()),
......
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(): ...@@ -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))
...@@ -12,7 +12,7 @@ import base64 ...@@ -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', ...@@ -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)
...@@ -188,22 +188,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): ...@@ -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
......
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
...@@ -3,45 +3,58 @@ ...@@ -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 formatting`_ which will be rendered by the self-documenting API.
YAML
----
YAML support is optional, and requires `PyYAML`_.
If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting
<http://daringfireball.net/projects/markdown/syntax>`_ which will be rendered by the HTML documenting renderer.
login/logout Login / Logout
--------------------------------- --------------
Django REST framework comes with a few views that can be useful including an api Django REST framework includes login and logout views that are useful if
login and logout views:: 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:: ...@@ -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
\ No newline at end of file
...@@ -40,8 +40,11 @@ Requirements ...@@ -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:: ...@@ -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 ...@@ -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
:mod:`utils`
==============
.. automodule:: utils
:members:
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): ...@@ -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): ...@@ -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})
"""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
......
...@@ -2,9 +2,9 @@ from djangorestframework.compat import View # Use Django 1.3's django.views.gen ...@@ -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): ...@@ -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
......
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): ...@@ -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): ...@@ -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):
......
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): ...@@ -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)
}, },
]) ])
......
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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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()
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): ...@@ -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): ...@@ -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): ...@@ -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)))
"""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)}
]) ])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment