From 578017e01d1da4746ae0045268043cfd74d41b42 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 29 Aug 2012 20:57:37 +0100 Subject: [PATCH] New docs --- AUTHORS | 7 +- CHANGELOG.rst | 102 -------- LICENSE => README.md | 56 +++++ README.rst | 83 ------ docs/check_sphinx.py | 9 - docs/conf.py | 228 ----------------- docs/contents.rst | 10 - docs/csrf.md | 4 + docs/examples.rst | 23 -- docs/examples/blogpost.rst | 37 --- docs/examples/modelviews.rst | 56 ----- docs/examples/objectstore.rst | 17 -- docs/examples/permissions.rst | 66 ----- docs/examples/pygments.rst | 89 ------- docs/examples/sandbox.rst | 12 - docs/examples/views.rst | 56 ----- docs/formoverloading.md | 46 ++++ docs/howto.rst | 8 - docs/howto/alternativeframeworks.rst | 35 --- docs/howto/mixin.rst | 30 --- docs/howto/requestmixin.rst | 75 ------ docs/howto/reverse.rst | 39 --- docs/howto/setup.rst | 73 ------ docs/howto/usingcurl.rst | 30 --- docs/howto/usingurllib2.rst | 39 --- docs/index.md | 62 +++++ docs/index.rst | 128 ---------- docs/library.rst | 8 - docs/library/authentication.rst | 5 - docs/library/compat.rst | 5 - docs/library/mixins.rst | 5 - docs/library/parsers.rst | 5 - docs/library/permissions.rst | 5 - docs/library/renderers.rst | 10 - docs/library/request.rst | 5 - docs/library/resource.rst | 5 - docs/library/response.rst | 5 - docs/library/reverse.rst | 5 - docs/library/serializer.rst | 5 - docs/library/status.rst | 5 - docs/library/utils.rst | 5 - docs/library/views.rst | 5 - docs/parsers.md | 5 + docs/renderers.md | 6 + docs/request.md | 76 ++++++ docs/requirements.txt | 8 - docs/response.md | 27 ++ docs/serializers.md | 47 ++++ docs/status.md | 17 ++ docs/templates/layout.html | 28 --- docs/tutorial/1-serialization.md | 236 ++++++++++++++++++ docs/tutorial/2-requests-and-responses.md | 137 ++++++++++ docs/tutorial/3-class-based-views.md | 137 ++++++++++ ...thentication-permissions-and-throttling.md | 3 + .../5-relationships-and-hyperlinked-apis.md | 9 + .../6-resource-orientated-projects.md | 49 ++++ docs/urls.md | 42 ++++ docs/views.md | 43 ++++ requirements.txt | 8 +- 59 files changed, 1006 insertions(+), 1375 deletions(-) delete mode 100644 CHANGELOG.rst rename LICENSE => README.md (51%) delete mode 100644 README.rst delete mode 100644 docs/check_sphinx.py delete mode 100644 docs/conf.py delete mode 100644 docs/contents.rst create mode 100644 docs/csrf.md delete mode 100644 docs/examples.rst delete mode 100644 docs/examples/blogpost.rst delete mode 100644 docs/examples/modelviews.rst delete mode 100644 docs/examples/objectstore.rst delete mode 100644 docs/examples/permissions.rst delete mode 100644 docs/examples/pygments.rst delete mode 100644 docs/examples/sandbox.rst delete mode 100644 docs/examples/views.rst create mode 100644 docs/formoverloading.md delete mode 100644 docs/howto.rst delete mode 100644 docs/howto/alternativeframeworks.rst delete mode 100644 docs/howto/mixin.rst delete mode 100644 docs/howto/requestmixin.rst delete mode 100644 docs/howto/reverse.rst delete mode 100644 docs/howto/setup.rst delete mode 100644 docs/howto/usingcurl.rst delete mode 100644 docs/howto/usingurllib2.rst create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/library.rst delete mode 100644 docs/library/authentication.rst delete mode 100644 docs/library/compat.rst delete mode 100644 docs/library/mixins.rst delete mode 100644 docs/library/parsers.rst delete mode 100644 docs/library/permissions.rst delete mode 100644 docs/library/renderers.rst delete mode 100644 docs/library/request.rst delete mode 100644 docs/library/resource.rst delete mode 100644 docs/library/response.rst delete mode 100644 docs/library/reverse.rst delete mode 100644 docs/library/serializer.rst delete mode 100644 docs/library/status.rst delete mode 100644 docs/library/utils.rst delete mode 100644 docs/library/views.rst create mode 100644 docs/parsers.md create mode 100644 docs/renderers.md create mode 100644 docs/request.md delete mode 100644 docs/requirements.txt create mode 100644 docs/response.md create mode 100644 docs/serializers.md create mode 100644 docs/status.md delete mode 100644 docs/templates/layout.html create mode 100644 docs/tutorial/1-serialization.md create mode 100644 docs/tutorial/2-requests-and-responses.md create mode 100644 docs/tutorial/3-class-based-views.md create mode 100644 docs/tutorial/4-authentication-permissions-and-throttling.md create mode 100644 docs/tutorial/5-relationships-and-hyperlinked-apis.md create mode 100644 docs/tutorial/6-resource-orientated-projects.md create mode 100644 docs/urls.md create mode 100644 docs/views.md diff --git a/AUTHORS b/AUTHORS index 47a31d05d..f75c94dd4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,5 @@ Tom Christie - tom@tomchristie.com, @_tomchristie -Marko Tibold (Additional thanks for providing & managing the Jenkins CI Server) +Marko Tibold Paul Bagwell Sébastien Piquemal Carmen Wick @@ -36,7 +36,4 @@ Daniel Izquierdo Can Yavuz Shawn Lewis -THANKS TO: - -Jesper Noehr & the django-piston contributors for providing the starting point for this project. -And of course, to the Django core team and the Django community at large. You guys rock. +Many thanks to everyone who's contributed to the project. \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index ddc3ac17c..000000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,102 +0,0 @@ -Release Notes -============= - -0.3.3 ------ - -* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions. -* Use `staticfiles` for css files. - - Easier to override. Won't conflict with customised admin styles (eg grappelli) -* Templates are now nicely namespaced. - - Allows easier overriding. -* Drop implied 'pk' filter if last arg in urlconf is unnamed. - - Too magical. Explict is better than implicit. -* Saner template variable autoescaping. -* Tider setup.py -* Updated for URLObject 2.0 -* Bugfixes: - - Bug with PerUserThrottling when user contains unicode chars. - -0.3.2 ------ - -* Bugfixes: - * Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115) - * serialize_model method in serializer.py may cause wrong value (#73) - * Fix Error when clicking OPTIONS button (#146) - * And many other fixes -* Remove short status codes - - Zen of Python: "There should be one-- and preferably only one --obvious way to do it." -* get_name, get_description become methods on the view - makes them overridable. -* Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering - -0.3.1 ------ - -* [not documented] - -0.3.0 ------ - -* JSONP Support -* Bugfixes, including support for latest markdown release - -0.2.4 ------ - -* Fix broken IsAdminUser permission. -* OPTIONS support. -* XMLParser. -* Drop mentions of Blog, BitBucket. - -0.2.3 ------ - -* Fix some throttling bugs. -* ``X-Throttle`` header on throttling. -* Support for nesting resources on related models. - -0.2.2 ------ - -* Throttling support complete. - -0.2.1 ------ - -* Couple of simple bugfixes over 0.2.0 - -0.2.0 ------ - -* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear. - The public API has been massively cleaned up. Expect it to be fairly stable from here on in. - -* ``Resource`` becomes decoupled into ``View`` and ``Resource``, your views should now inherit from ``View``, not ``Resource``. - -* The handler functions on views ``.get() .put() .post()`` etc, no longer have the ``content`` and ``auth`` args. - Use ``self.CONTENT`` inside a view to access the deserialized, validated content. - Use ``self.user`` inside a view to access the authenticated user. - -* ``allowed_methods`` and ``anon_allowed_methods`` are now defunct. if a method is defined, it's available. - The ``permissions`` attribute on a ``View`` is now used to provide generic permissions checking. - Use permission classes such as ``FullAnonAccess``, ``IsAuthenticated`` or ``IsUserOrIsAnonReadOnly`` to set the permissions. - -* The ``authenticators`` class becomes ``authentication``. Class names change to ``Authentication``. - -* The ``emitters`` class becomes ``renderers``. Class names change to ``Renderers``. - -* ``ResponseException`` becomes ``ErrorResponse``. - -* The mixin classes have been nicely refactored, the basic mixins are now ``RequestMixin``, ``ResponseMixin``, ``AuthMixin``, and ``ResourceMixin`` - You can reuse these mixin classes individually without using the ``View`` class. - -0.1.1 ------ - -* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1. - -0.1.0 ------ - -* Initial release. diff --git a/LICENSE b/README.md similarity index 51% rename from LICENSE rename to README.md index 025dccf1a..3b819e457 100644 --- a/LICENSE +++ b/README.md @@ -1,3 +1,51 @@ +# Django REST framework + +**A toolkit for building well-connected, self-describing web APIs.** + +**Author:** Tom Christie. [Follow me on Twitter][twitter] + +# Overview + +This branch is the redesign of Django REST framework. It is a work in progress. + +For more information, check out [the documentation][docs], in particular, the tutorial is recommended as the best place to get an overview of the redesign. + +# Requirements + +* Python (2.6, 2.7) +* Django (1.3, 1.4, 1.5) +* [URLObject] (>=2.0.0) + +**Optional:** + +* [Markdown] - Markdown support for the self describing API. +* [PyYAML] - YAML content type support. + +# Installation + +**Leaving these instructions in for the moment, they'll be valid once this becomes the master version** + +Install using `pip`... + + pip install djangorestframework + +...or clone the project from github. + + git clone git@github.com:tomchristie/django-rest-framework.git + pip install -r requirements.txt + +# Quickstart + +**TODO** + +# Changelog + +## 2.0.0 + +Redesign of core components. + +# License + Copyright (c) 2011, Tom Christie All rights reserved. @@ -20,3 +68,11 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +[twitter]: https://twitter.com/_tomchristie +[docs]: docs/index.md +[urlobject]: https://github.com/zacharyvoase/urlobject +[markdown]: http://pypi.python.org/pypi/Markdown/ +[pyyaml]: http://pypi.python.org/pypi/PyYAML + diff --git a/README.rst b/README.rst deleted file mode 100644 index 23a8075e8..000000000 --- a/README.rst +++ /dev/null @@ -1,83 +0,0 @@ -Django REST framework -===================== - -**Django REST framework makes it easy to build well-connected, self-describing RESTful Web APIs.** - -**Author:** Tom Christie. `Follow me on Twitter `_. - -Overview -======== - -Features: - -* Creates awesome self-describing *web browse-able* APIs. -* Clean, modular design, using Django's class based views. -* Easily extended for custom content types, serialization formats and authentication policies. -* Stable, well tested code-base. -* Active developer community. - -Full documentation for the project is available at http://django-rest-framework.org - -Issue tracking is on `GitHub `_. -General questions should be taken to the `discussion group `_. - -We also have a `Jenkins service `_ which runs our test suite. - -Requirements: - -* Python (2.5, 2.6, 2.7 supported) -* Django (1.2, 1.3, 1.4-alpha supported) - - -Installation Notes -================== - -To clone the project from GitHub using git:: - - git clone git@github.com:tomchristie/django-rest-framework.git - - -To install django-rest-framework in a virtualenv environment:: - - cd django-rest-framework - virtualenv --no-site-packages --distribute env - source env/bin/activate - pip install -r requirements.txt # django, coverage - - -To run the tests:: - - export PYTHONPATH=. # Ensure djangorestframework is on the PYTHONPATH - python djangorestframework/runtests/runtests.py - - -To run the test coverage report:: - - export PYTHONPATH=. # Ensure djangorestframework is on the PYTHONPATH - python djangorestframework/runtests/runcoverage.py - - -To run the examples:: - - pip install -r examples/requirements.txt # pygments, httplib2, markdown - cd examples - export PYTHONPATH=.. - python manage.py syncdb - python manage.py runserver - - -To build the documentation:: - - pip install -r docs/requirements.txt # sphinx - sphinx-build -c docs -b html -d docs/build docs html - - -To run the tests against the full set of supported configurations:: - - deactivate # Ensure we are not currently running in a virtualenv - tox - - -To create the sdist packages:: - - python setup.py sdist --formats=gztar,zip diff --git a/docs/check_sphinx.py b/docs/check_sphinx.py deleted file mode 100644 index feb04abd2..000000000 --- a/docs/check_sphinx.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest -import subprocess - -def test_build_docs(tmpdir): - doctrees = tmpdir.join("doctrees") - htmldir = "html" #we want to keep the docs - subprocess.check_call([ - "sphinx-build", "-q", "-bhtml", - "-d", str(doctrees), ".", str(htmldir)]) diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 163888142..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Asset Platform documentation build configuration file, created by -# sphinx-quickstart on Fri Nov 19 20:24:09 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'djangorestframework')) # for documenting the library -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'examples')) # for importing settings -import settings -from django.core.management import setup_environ -setup_environ(settings) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'django-rest-framework' -copyright = u'2011, Tom Christie' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. - -import djangorestframework - -version = djangorestframework.__version__ - -# The full version, including alpha/beta/rc tags. -release = version - -autodoc_member_order='bysource' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'sphinxdoc' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = "Django REST framework" - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -#htmlhelp_basename = '' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -#latex_documents = [ -# (), -#] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -#man_pages = [ -# () -#] - -linkcheck_timeout = 120 # seconds, set to extra large value for link_checks diff --git a/docs/contents.rst b/docs/contents.rst deleted file mode 100644 index d8e6e7428..000000000 --- a/docs/contents.rst +++ /dev/null @@ -1,10 +0,0 @@ -Documentation -============= - -.. toctree:: - :maxdepth: 2 - - howto - library - examples - diff --git a/docs/csrf.md b/docs/csrf.md new file mode 100644 index 000000000..8e0b94805 --- /dev/null +++ b/docs/csrf.md @@ -0,0 +1,4 @@ +REST framework and CSRF protection +================================== + +> "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability -- very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one." - Jeff Atwood \ No newline at end of file diff --git a/docs/examples.rst b/docs/examples.rst deleted file mode 100644 index 640883454..000000000 --- a/docs/examples.rst +++ /dev/null @@ -1,23 +0,0 @@ -Examples -======== - -There are a few real world web API examples included with Django REST framework. - -#. :doc:`examples/objectstore` - Using :class:`views.View` classes for APIs that do not map to models. -#. :doc:`examples/pygments` - Using :class:`views.View` classes with forms for input validation. -#. :doc:`examples/blogpost` - Using :class:`views.ModelView` classes for APIs that map directly to models. - -All the examples are freely available for testing in the sandbox: - - http://rest.ep.io - -(The :doc:`examples/sandbox` resource is also documented.) - -Example Reference ------------------ - -.. toctree:: - :maxdepth: 1 - :glob: - - examples/* diff --git a/docs/examples/blogpost.rst b/docs/examples/blogpost.rst deleted file mode 100644 index 11e376efe..000000000 --- a/docs/examples/blogpost.rst +++ /dev/null @@ -1,37 +0,0 @@ -Blog Posts API -============== - -* http://rest.ep.io/blog-post/ - -The models ----------- - -In this example we're working from two related models: - -``models.py`` - -.. include:: ../../examples/blogpost/models.py - :literal: - -Creating the resources ----------------------- - -We need to create two resources that we map to our two existing models, in order to describe how the models should be serialized. -Our resource descriptions will typically go into a module called something like 'resources.py' - -``resources.py`` - -.. include:: ../../examples/blogpost/resources.py - :literal: - -Creating views for our resources --------------------------------- - -Once we've created the resources there's very little we need to do to create the API. -For each resource we'll create a base view, and an instance view. -The generic views :class:`.ListOrCreateModelView` and :class:`.InstanceModelView` provide default operations for listing, creating and updating our models via the API, and also automatically provide input validation using default ModelForms for each model. - -``urls.py`` - -.. include:: ../../examples/blogpost/urls.py - :literal: diff --git a/docs/examples/modelviews.rst b/docs/examples/modelviews.rst deleted file mode 100644 index b67d50d9e..000000000 --- a/docs/examples/modelviews.rst +++ /dev/null @@ -1,56 +0,0 @@ -Getting Started - Model Views ------------------------------ - -.. note:: - - A live sandbox instance of this API is available: - - http://rest.ep.io/model-resource-example/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/resource-example/ -H 'Accept: text/plain' - -Often you'll want parts of your API to directly map to existing django models. Django REST framework handles this nicely for you in a couple of ways: - -#. It automatically provides suitable create/read/update/delete methods for your views. -#. Input validation occurs automatically, by using appropriate `ModelForms `_. - -Here's the model we're working from in this example: - -``models.py`` - -.. include:: ../../examples/modelresourceexample/models.py - :literal: - -To add an API for the model, first we need to create a Resource for the model. - -``resources.py`` - -.. include:: ../../examples/modelresourceexample/resources.py - :literal: - -Then we simply map a couple of views to the Resource in our urlconf. - -``urls.py`` - -.. include:: ../../examples/modelresourceexample/urls.py - :literal: - -And we're done. We've now got a fully browseable API, which supports multiple input and output media types, and has all the nice automatic field validation that Django gives us for free. - -We can visit the API in our browser: - -* http://rest.ep.io/model-resource-example/ - -Or access it from the command line using curl: - -.. code-block:: bash - - # Demonstrates API's input validation using form input - bash: curl -X POST --data 'foo=true' http://rest.ep.io/model-resource-example/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} - - # Demonstrates API's input validation using JSON input - bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://rest.ep.io/model-resource-example/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} diff --git a/docs/examples/objectstore.rst b/docs/examples/objectstore.rst deleted file mode 100644 index 0939fe9c7..000000000 --- a/docs/examples/objectstore.rst +++ /dev/null @@ -1,17 +0,0 @@ -Object Store API -================ - -* http://rest.ep.io/object-store/ - -This example shows an object store API that can be used to store arbitrary serializable content. - -``urls.py`` - -.. include:: ../../examples/objectstore/urls.py - :literal: - -``views.py`` - -.. include:: ../../examples/objectstore/views.py - :literal: - diff --git a/docs/examples/permissions.rst b/docs/examples/permissions.rst deleted file mode 100644 index eafc32555..000000000 --- a/docs/examples/permissions.rst +++ /dev/null @@ -1,66 +0,0 @@ -Permissions -=========== - -This example will show how you can protect your api by using authentication -and how you can limit the amount of requests a user can do to a resource by setting -a throttle to your view. - -Authentication --------------- - -If you want to protect your api from unauthorized users, Django REST Framework -offers you two default authentication methods: - - * Basic Authentication - * Django's session-based authentication - -These authentication methods are by default enabled. But they are not used unless -you specifically state that your view requires authentication. - -To do this you just need to import the `Isauthenticated` class from the frameworks' `permissions` module.:: - - from djangorestframework.permissions import IsAuthenticated - -Then you enable authentication by setting the right 'permission requirement' to the `permissions` class attribute of your View like -the example View below.: - - -.. literalinclude:: ../../examples/permissionsexample/views.py - :pyobject: LoggedInExampleView - -The `IsAuthenticated` permission will only let a user do a 'GET' if he is authenticated. Try it -yourself on the live sandbox__ - -__ http://rest.ep.io/permissions-example/loggedin - - -Throttling ----------- - -If you want to limit the amount of requests a client is allowed to do on -a resource, then you can set a 'throttle' to achieve this. - -For this to work you'll need to import the `PerUserThrottling` class from the `permissions` -module.:: - - from djangorestframework.permissions import PerUserThrottling - -In the example below we have limited the amount of requests one 'client' or 'user' -may do on our view to 10 requests per minute.: - -.. literalinclude:: ../../examples/permissionsexample/views.py - :pyobject: ThrottlingExampleView - -Try it yourself on the live sandbox__. - -__ http://rest.ep.io/permissions-example/throttling - -Now if you want a view to require both aurhentication and throttling, you simply declare them -both:: - - permissions = (PerUserThrottling, Isauthenticated) - -To see what other throttles are available, have a look at the :mod:`permissions` module. - -If you want to implement your own authentication method, then refer to the :mod:`authentication` -module. diff --git a/docs/examples/pygments.rst b/docs/examples/pygments.rst deleted file mode 100644 index 4e72f754d..000000000 --- a/docs/examples/pygments.rst +++ /dev/null @@ -1,89 +0,0 @@ -Code Highlighting API -===================== - -This example demonstrates creating a REST API using a :class:`.Resource` with some form validation on the input. -We're going to provide a simple wrapper around the awesome `pygments `_ library, to create the Web API for a simple pastebin. - -.. note:: - - A live sandbox instance of this API is available at http://rest.ep.io/pygments/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/pygments/ -H 'Accept: text/plain' - - -URL configuration ------------------ - -We'll need two resources: - -* A resource which represents the root of the API. -* A resource which represents an instance of a highlighted snippet. - -``urls.py`` - -.. include:: ../../examples/pygments_api/urls.py - :literal: - -Form validation ---------------- - -We'll now add a form to specify what input fields are required when creating a new highlighted code snippet. This will include: - -* The code text itself. -* An optional title for the code. -* A flag to determine if line numbers should be included. -* Which programming language to interpret the code snippet as. -* Which output style to use for the highlighting. - -``forms.py`` - -.. include:: ../../examples/pygments_api/forms.py - :literal: - -Creating the resources ----------------------- - -We'll need to define 3 resource handling methods on our resources. - -* ``PygmentsRoot.get()`` method, which lists all the existing snippets. -* ``PygmentsRoot.post()`` method, which creates new snippets. -* ``PygmentsInstance.get()`` method, which returns existing snippets. - -And set a number of attributes on our resources. - -* Set the ``allowed_methods`` and ``anon_allowed_methods`` attributes on both resources allowing for full unauthenticated access. -* Set the ``form`` attribute on the ``PygmentsRoot`` resource, to give us input validation when we create snippets. -* Set the ``emitters`` attribute on the ``PygmentsInstance`` resource, so that - -``views.py`` - -.. include:: ../../examples/pygments_api/views.py - :literal: - -Completed ---------- - -And we're done. We now have an API that is: - -* **Browseable.** The API supports media types for both programmatic and human access, and can be accessed either via a browser or from the command line. -* **Self describing.** The API serves as it's own documentation. -* **Well connected.** The API can be accessed fully by traversal from the initial URL. Clients never need to construct URLs themselves. - -Our API also supports multiple media types for both input and output, and applies sensible input validation in all cases. - -For example if we make a POST request using form input: - -.. code-block:: bash - - bash: curl -X POST --data 'code=print "hello, world!"' --data 'style=foobar' -H 'X-Requested-With: XMLHttpRequest' http://rest.ep.io/pygments/ - {"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}} - -Or if we make the same request using JSON: - -.. code-block:: bash - - bash: curl -X POST --data-binary '{"code":"print \"hello, world!\"", "style":"foobar"}' -H 'Content-Type: application/json' -H 'X-Requested-With: XMLHttpRequest' http://rest.ep.io/pygments/ - {"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}} - diff --git a/docs/examples/sandbox.rst b/docs/examples/sandbox.rst deleted file mode 100644 index ec465aafe..000000000 --- a/docs/examples/sandbox.rst +++ /dev/null @@ -1,12 +0,0 @@ -Sandbox Root API -================ - -The Resource ------------- - -The root level resource of the Django REST framework examples is a simple read only resource: - -``view.py`` - -.. include:: ../../examples/sandbox/views.py - :literal: diff --git a/docs/examples/views.rst b/docs/examples/views.rst deleted file mode 100644 index db0db0d70..000000000 --- a/docs/examples/views.rst +++ /dev/null @@ -1,56 +0,0 @@ -Getting Started - Views ------------------------ - -.. note:: - - A live sandbox instance of this API is available: - - http://rest.ep.io/resource-example/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/resource-example/ -H 'Accept: text/plain' - -We're going to start off with a simple example, that demonstrates a few things: - -#. Creating views. -#. Linking views. -#. Writing method handlers on views. -#. Adding form validation to views. - -First we'll define two views in our urlconf. - -``urls.py`` - -.. include:: ../../examples/resourceexample/urls.py - :literal: - -Now we'll add a form that we'll use for input validation. This is completely optional, but it's often useful. - -``forms.py`` - -.. include:: ../../examples/resourceexample/forms.py - :literal: - -Now we'll write our views. The first is a read only view that links to three instances of the second. The second view just has some stub handler methods to help us see that our example is working. - -``views.py`` - -.. include:: ../../examples/resourceexample/views.py - :literal: - -That's us done. Our API now provides both programmatic access using JSON and XML, as well a nice browseable HTML view, so we can now access it both from the browser: - -* http://rest.ep.io/resource-example/ - -And from the command line: - -.. code-block:: bash - - # Demonstrates API's input validation using form input - bash: curl -X POST --data 'foo=true' http://rest.ep.io/resource-example/1/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} - - # Demonstrates API's input validation using JSON input - bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://rest.ep.io/resource-example/1/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} diff --git a/docs/formoverloading.md b/docs/formoverloading.md new file mode 100644 index 000000000..cab47db95 --- /dev/null +++ b/docs/formoverloading.md @@ -0,0 +1,46 @@ +Supporting browser-based PUT & DELETE +===================================== + +> "There are two noncontroversial uses for overloaded POST. The first is to *simulate* HTTP's uniform interface for clients like web browsers that don't support PUT or DELETE" - [RESTful Web Services](1), Leonard Richardson & Sam Ruby. + +This is the same strategy as is used in [Ruby on Rails](2). + +Overloading the HTTP method +--------------------------- + +For example, given the following form: + +
+ +
+ +`request.method` would return `"DELETE"`. + +Overloading the HTTP content type +--------------------------------- + +Browser-based submission of content types other than form are supported by using form fields named `_content` and `_content_type`: + +For example, given the following form: + +
+ + +
+ +`request.content_type` would return `"application/json"`, and `request.content` would return `"{'count': 1}"` + +Why not just use Javascript? +============================ + +**[TODO]** + +Doesn't HTML5 support PUT and DELETE forms? +=========================================== + +Nope. It was at one point intended to support `PUT` and `DELETE` forms, but was later [dropped from the spec](3). There remains [ongoing discussion](4) about adding support for `PUT` and `DELETE`, as well as how to support content-types other than form-encoded data. + +[1]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260 +[2]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work +[3]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24 +[4]: http://amundsen.com/examples/put-delete-forms/ diff --git a/docs/howto.rst b/docs/howto.rst deleted file mode 100644 index 8fdc09267..000000000 --- a/docs/howto.rst +++ /dev/null @@ -1,8 +0,0 @@ -How Tos, FAQs & Notes -===================== - -.. toctree:: - :maxdepth: 1 - :glob: - - howto/* diff --git a/docs/howto/alternativeframeworks.rst b/docs/howto/alternativeframeworks.rst deleted file mode 100644 index dc8d1ea65..000000000 --- a/docs/howto/alternativeframeworks.rst +++ /dev/null @@ -1,35 +0,0 @@ -Alternative frameworks & Why Django REST framework -================================================== - -Alternative frameworks ----------------------- - -There are a number of alternative REST frameworks for Django: - -* `django-piston `_ is very mature, and has a large community behind it. This project was originally based on piston code in parts. -* `django-tasypie `_ is also very good, and has a very active and helpful developer community and maintainers. -* Other interesting projects include `dagny `_ and `dj-webmachine `_ - - -Why use Django REST framework? ------------------------------- - -The big benefits of using Django REST framework come down to: - -1. It's based on Django's class based views, which makes it simple, modular, and future-proof. -2. It stays as close as possible to Django idioms and language throughout. -3. The browse-able API makes working with the APIs extremely quick and easy. - - -Why was this project created? ------------------------------ - -For me the browse-able API is the most important aspect of Django REST framework. - -I wanted to show that Web APIs could easily be made Web browse-able, -and demonstrate how much better browse-able Web APIs are to work with. - -Being able to navigate and use a Web API directly in the browser is a huge win over only having command line and programmatic -access to the API. It enables the API to be properly self-describing, and it makes it much much quicker and easier to work with. -There's no fundamental reason why the Web APIs we're creating shouldn't be able to render to HTML as well as JSON/XML/whatever, -and I really think that more Web API frameworks *in whatever language* ought to be taking a similar approach. diff --git a/docs/howto/mixin.rst b/docs/howto/mixin.rst deleted file mode 100644 index 1a84f2ad4..000000000 --- a/docs/howto/mixin.rst +++ /dev/null @@ -1,30 +0,0 @@ -Using Django REST framework Mixin classes -========================================= - -This example demonstrates creating a REST API **without** using Django REST framework's :class:`.Resource` or :class:`.ModelResource`, but instead using Django's :class:`View` class, and adding the :class:`ResponseMixin` class to provide full HTTP Accept header content negotiation, -a browseable Web API, and much of the other goodness that Django REST framework gives you for free. - -.. note:: - - A live sandbox instance of this API is available for testing: - - * http://rest.ep.io/mixin/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/mixin/ - - -URL configuration ------------------ - -Everything we need for this example can go straight into the URL conf... - -``urls.py`` - -.. include:: ../../examples/mixin/urls.py - :literal: - -That's it. Auto-magically our API now supports multiple output formats, specified either by using -standard HTTP Accept header content negotiation, or by using the `&_accept=application/json` style parameter overrides. -We even get a nice HTML view which can be used to self-document our API. diff --git a/docs/howto/requestmixin.rst b/docs/howto/requestmixin.rst deleted file mode 100644 index c0aadb3f7..000000000 --- a/docs/howto/requestmixin.rst +++ /dev/null @@ -1,75 +0,0 @@ -Using the enhanced request in all your views -============================================== - -This example shows how you can use Django REST framework's enhanced `request` - :class:`request.Request` - in your own views, without having to use the full-blown :class:`views.View` class. - -What can it do for you ? Mostly, it will take care of parsing the request's content, and handling equally all HTTP methods ... - -Before --------- - -In order to support `JSON` or other serial formats, you might have parsed manually the request's content with something like : :: - - class MyView(View): - - def put(self, request, *args, **kwargs): - content_type = request.META['CONTENT_TYPE'] - if (content_type == 'application/json'): - raw_data = request.read() - parsed_data = json.loads(raw_data) - - # PLUS as many `elif` as formats you wish to support ... - - # then do stuff with your data : - self.do_stuff(parsed_data['bla'], parsed_data['hoho']) - - # and finally respond something - -... and you were unhappy because this looks hackish. - -Also, you might have tried uploading files with a PUT request - *and given up* since that's complicated to achieve even with Django 1.3. - - -After ------- - -All the dirty `Content-type` checking and content reading and parsing is done for you, and you only need to do the following : :: - - class MyView(MyBaseViewUsingEnhancedRequest): - - def put(self, request, *args, **kwargs): - self.do_stuff(request.DATA['bla'], request.DATA['hoho']) - # and finally respond something - -So the parsed content is magically available as `.DATA` on the `request` object. - -Also, if you uploaded files, they are available as `.FILES`, like with a normal POST request. - -.. note:: Note that all the above is also valid for a POST request. - - -How to add it to your custom views ? --------------------------------------- - -Now that you're convinced you need to use the enhanced request object, here is how you can add it to all your custom views : :: - - from django.views.generic.base import View - - from djangorestframework.mixins import RequestMixin - from djangorestframework import parsers - - - class MyBaseViewUsingEnhancedRequest(RequestMixin, View): - """ - Base view enabling the usage of enhanced requests with user defined views. - """ - - parser_classes = parsers.DEFAULT_PARSERS - - def dispatch(self, request, *args, **kwargs): - request = self.prepare_request(request) - return super(MyBaseViewUsingEnhancedRequest, self).dispatch(request, *args, **kwargs) - -And then, use this class as a base for all your custom views. - -.. note:: you can see this live in the examples. diff --git a/docs/howto/reverse.rst b/docs/howto/reverse.rst deleted file mode 100644 index 73b8fa4df..000000000 --- a/docs/howto/reverse.rst +++ /dev/null @@ -1,39 +0,0 @@ -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 diff --git a/docs/howto/setup.rst b/docs/howto/setup.rst deleted file mode 100644 index f01270601..000000000 --- a/docs/howto/setup.rst +++ /dev/null @@ -1,73 +0,0 @@ -.. _setup: - -Setup -===== - -Templates ---------- - -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. - -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:: - - {% extends "djangorestframework/base.html" %} - - {% block title %}My API{% endblock %} - - {% block branding %} -

My API

- {% endblock %} - - -Styling -------- - -Django REST framework requires `django.contrib.staticfiles`_ to serve it's css. -If you're using Django 1.2 you'll need to use the seperate -`django-staticfiles`_ package instead. - -You can override the styling by creating a file in your top-level static -directory named ``djangorestframework/css/style.css`` - - -Markdown --------- - -`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`_. - - -Login / Logout --------------- - -Django REST framework includes login and logout views that are needed if -you're using the self-documenting API. - -Make sure you include the following in your `urlconf`:: - - from django.conf.urls.defaults import patterns, url - - urlpatterns = patterns('', - ... - url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework')) - ) - -.. _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 diff --git a/docs/howto/usingcurl.rst b/docs/howto/usingcurl.rst deleted file mode 100644 index eeb8da062..000000000 --- a/docs/howto/usingcurl.rst +++ /dev/null @@ -1,30 +0,0 @@ -Using CURL with django-rest-framework -===================================== - -`curl `_ is a great command line tool for making requests to URLs. - -There are a few things that can be helpful to remember when using CURL with django-rest-framework APIs. - -#. Curl sends an ``Accept: */*`` header by default:: - - curl -X GET http://example.com/my-api/ - -#. Setting the ``Accept:`` header on a curl request can be useful:: - - curl -X GET -H 'Accept: application/json' http://example.com/my-api/ - -#. The text/plain representation is useful for browsing the API:: - - curl -X GET -H 'Accept: text/plain' http://example.com/my-api/ - -#. ``POST`` and ``PUT`` requests can contain form data (ie ``Content-Type: application/x-www-form-urlencoded``):: - - curl -X PUT --data 'foo=bar' http://example.com/my-api/some-resource/ - -#. Or any other content type:: - - curl -X PUT -H 'Content-Type: application/json' --data-binary '{"foo":"bar"}' http://example.com/my-api/some-resource/ - -#. You can use basic authentication to send the username and password:: - - curl -X GET -H 'Accept: application/json' -u : http://example.com/my-api/ diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst deleted file mode 100644 index 6320dc208..000000000 --- a/docs/howto/usingurllib2.rst +++ /dev/null @@ -1,39 +0,0 @@ -Using urllib2 with Django REST Framework -======================================== - -Python's standard library comes with some nice modules -you can use to test your api or even write a full client. - -Using the 'GET' method ----------------------- - -Here's an example which does a 'GET' on the `model-resource` example -in the sandbox.:: - - >>> import urllib2 - >>> r = urllib2.urlopen('htpp://rest.ep.io/model-resource-example') - >>> r.getcode() # Check if the response was ok - 200 - >>> print r.read() # Examin the response itself - [{"url": "http://rest.ep.io/model-resource-example/1/", "baz": "sdf", "foo": true, "bar": 123}] - -Using the 'POST' method ------------------------ - -And here's an example which does a 'POST' to create a new instance. First let's encode -the data we want to POST. We'll use `urllib` for encoding and the `time` module -to send the current time as as a string value for our POST.:: - - >>> import urllib, time - >>> d = urllib.urlencode((('bar', 123), ('baz', time.asctime()))) - -Now use the `Request` class and specify the 'Content-type':: - - >>> req = urllib2.Request('http://rest.ep.io/model-resource-example/', data=d, headers={'Content-Type':'application/x-www-form-urlencoded'}) - >>> resp = urllib2.urlopen(req) - >>> resp.getcode() - 201 - >>> resp.read() - '{"url": "http://rest.ep.io/model-resource-example/4/", "baz": "Fri Dec 30 18:22:52 2011", "foo": false, "bar": 123}' - -That should get you started to write a client for your own api. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..9bd90d8dc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,62 @@ +Quickstart +========== + +**TODO** + +Tutorial +======== + +* [1 - Serialization][tut-1] +* [2 - Requests & Responses][tut-2] +* [3 - Class based views][tut-3] +* [4 - Authentication, permissions & throttling][tut-4] +* [5 - Relationships & hyperlinked APIs][tut-5] +* [6 - Resource orientated projects][tut-6] + +API Guide +========= + +* [Requests][request] +* [Responses][response] +* [Views][views] +* [Parsers][parsers] +* [Renderers][renderers] +* [Serializers][serializers] +* [Authentication][authentication] +* [Permissions][permissions] +* [Status codes][status] + +Topics +====== + +* [Returning URLs][urls] +* [CSRF][csrf] +* [Form overloading][formoverloading] + +Other +===== + +* Why REST framework +* Contributing +* Change Log + +[tut-1]: tutorial/1-serialization.md +[tut-2]: tutorial/2-requests-and-responses.md +[tut-3]: tutorial/3-class-based-views.md +[tut-4]: tutorial/4-authentication-permissions-and-throttling.md +[tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md +[tut-6]: tutorial/6-resource-orientated-projects.md + +[request]: request.md +[response]: response.md +[views]: views.md +[parsers]: parsers.md +[renderers]: renderers.md +[serializers]: serializers.md +[authentication]: authentication.md +[permissions]: permissions.md +[status]: status.md + +[urls]: urls.md +[csrf]: csrf.md +[formoverloading]: formoverloading.md diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index a6745fca5..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,128 +0,0 @@ -.. meta:: - :description: A lightweight REST framework for Django. - :keywords: django, python, REST, RESTful, API, interface, framework - - -Django REST framework -===================== - -Introduction ------------- - -Django REST framework is a lightweight REST framework for Django, that aims to make it easy to build well-connected, self-describing RESTful Web APIs. - -**Browse example APIs created with Django REST framework:** `The Sandbox `_ - -Features: ---------- - -* Automatically provides an awesome Django admin style `browse-able self-documenting API `_. -* Clean, simple, views for Resources, using Django's new `class based views `_. -* Support for ModelResources with out-of-the-box default implementations and input validation. -* Pluggable :mod:`.parsers`, :mod:`renderers`, :mod:`authentication` and :mod:`permissions` - Easy to customise. -* Content type negotiation using HTTP Accept headers. -* Optional support for forms as input validation. -* Modular architecture - MixIn classes can be used without requiring the :class:`.Resource` or :class:`.ModelResource` classes. - -Resources ---------- - -**Project hosting:** `GitHub `_. - -* The ``djangorestframework`` package is `available on PyPI `_. -* We have an active `discussion group `_. -* Bug reports are handled on the `issue tracker `_. -* There is a `Jenkins CI server `_ which tracks test status and coverage reporting. (Thanks Marko!) - -Any and all questions, thoughts, bug reports and contributions are *hugely appreciated*. - -Requirements ------------- - -* Python (2.5, 2.6, 2.7 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 ------------- - -You can install Django REST framework using ``pip`` or ``easy_install``:: - - pip install djangorestframework - -Or get the latest development version using git:: - - git clone git@github.com:tomchristie/django-rest-framework.git - -Setup ------ - -To add Django REST framework to a Django project: - -* Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``. -* Add ``djangorestframework`` to your ``INSTALLED_APPS``. -* Add the following to your URLconf. (To include the REST framework Login/Logout views.):: - - urlpatterns = patterns('', - ... - url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework')) - ) - -For more information on settings take a look at the :ref:`setup` section. - -Getting Started ---------------- - -Using Django REST framework can be as simple as adding a few lines to your urlconf. - -The following example exposes your `MyModel` model through an api. It will provide two views: - - * A view which lists your model instances and simultaniously allows creation of instances - from that view. - - * Another view which lets you view, update or delete your model instances individually. - -``urls.py``:: - - from django.conf.urls.defaults import patterns, url - from djangorestframework.resources import ModelResource - from djangorestframework.views import ListOrCreateModelView, InstanceModelView - from myapp.models import MyModel - - class MyResource(ModelResource): - model = MyModel - - urlpatterns = patterns('', - url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)), - url(r'^(?P[^/]+)/$', InstanceModelView.as_view(resource=MyResource)), - ) - -.. include:: howto.rst - -.. include:: library.rst - - -.. include:: examples.rst - -.. toctree:: - :hidden: - - contents - -.. include:: ../CHANGELOG.rst - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ -.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/ -.. _URLObject: http://pypi.python.org/pypi/URLObject/ -.. _Markdown: http://pypi.python.org/pypi/Markdown/ -.. _PyYAML: http://pypi.python.org/pypi/PyYAML diff --git a/docs/library.rst b/docs/library.rst deleted file mode 100644 index b0309da0c..000000000 --- a/docs/library.rst +++ /dev/null @@ -1,8 +0,0 @@ -Library -======= - -.. toctree:: - :maxdepth: 1 - :glob: - - library/* diff --git a/docs/library/authentication.rst b/docs/library/authentication.rst deleted file mode 100644 index d159f6054..000000000 --- a/docs/library/authentication.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`authentication` -===================== - -.. automodule:: authentication - :members: diff --git a/docs/library/compat.rst b/docs/library/compat.rst deleted file mode 100644 index 93fb081a0..000000000 --- a/docs/library/compat.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`compat` -===================== - -.. automodule:: compat - :members: diff --git a/docs/library/mixins.rst b/docs/library/mixins.rst deleted file mode 100644 index 04bf66b06..000000000 --- a/docs/library/mixins.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`mixins` -===================== - -.. automodule:: mixins - :members: diff --git a/docs/library/parsers.rst b/docs/library/parsers.rst deleted file mode 100644 index 48d762a51..000000000 --- a/docs/library/parsers.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`parsers` -============== - -.. automodule:: parsers - :members: diff --git a/docs/library/permissions.rst b/docs/library/permissions.rst deleted file mode 100644 index c694d639a..000000000 --- a/docs/library/permissions.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`permissions` -===================== - -.. automodule:: permissions - :members: diff --git a/docs/library/renderers.rst b/docs/library/renderers.rst deleted file mode 100644 index a9e729316..000000000 --- a/docs/library/renderers.rst +++ /dev/null @@ -1,10 +0,0 @@ -:mod:`renderers` -================ - -The renderers module provides a set of renderers that can be plugged in to a :class:`.Resource`. -A renderer is responsible for taking the output of a View and serializing it to a given media type. -A :class:`.Resource` can have a number of renderers, allow the same content to be serialized in a number -of different formats depending on the requesting client's preferences, as specified in the HTTP Request's Accept header. - -.. automodule:: renderers - :members: diff --git a/docs/library/request.rst b/docs/library/request.rst deleted file mode 100644 index 5e99826ad..000000000 --- a/docs/library/request.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`request` -===================== - -.. automodule:: request - :members: diff --git a/docs/library/resource.rst b/docs/library/resource.rst deleted file mode 100644 index 2a95051bf..000000000 --- a/docs/library/resource.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`resource` -=============== - -.. automodule:: resources - :members: diff --git a/docs/library/response.rst b/docs/library/response.rst deleted file mode 100644 index c2fff5a7f..000000000 --- a/docs/library/response.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`response` -=============== - -.. automodule:: response - :members: diff --git a/docs/library/reverse.rst b/docs/library/reverse.rst deleted file mode 100644 index a2c29c488..000000000 --- a/docs/library/reverse.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`reverse` -================ - -.. automodule:: reverse - :members: diff --git a/docs/library/serializer.rst b/docs/library/serializer.rst deleted file mode 100644 index 63dd33089..000000000 --- a/docs/library/serializer.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`serializer` -================= - -.. automodule:: serializer - :members: diff --git a/docs/library/status.rst b/docs/library/status.rst deleted file mode 100644 index 0c7596bc2..000000000 --- a/docs/library/status.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`status` -=============== - -.. automodule:: status - :members: diff --git a/docs/library/utils.rst b/docs/library/utils.rst deleted file mode 100644 index 653f24fde..000000000 --- a/docs/library/utils.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`utils` -============== - -.. automodule:: utils - :members: diff --git a/docs/library/views.rst b/docs/library/views.rst deleted file mode 100644 index 329b487b7..000000000 --- a/docs/library/views.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`views` -===================== - -.. automodule:: views - :members: diff --git a/docs/parsers.md b/docs/parsers.md new file mode 100644 index 000000000..44e331052 --- /dev/null +++ b/docs/parsers.md @@ -0,0 +1,5 @@ +Parsers +======= + +.parse(request) +--------------- diff --git a/docs/renderers.md b/docs/renderers.md new file mode 100644 index 000000000..20cdb8ada --- /dev/null +++ b/docs/renderers.md @@ -0,0 +1,6 @@ +Renderers +========= + +.render(response) +----------------- + diff --git a/docs/request.md b/docs/request.md new file mode 100644 index 000000000..b0491897a --- /dev/null +++ b/docs/request.md @@ -0,0 +1,76 @@ +Request +======= + +> If you're doing REST-based web service stuff ... you should ignore request.POST. +> +> — Malcom Tredinnick, [Django developers group][1] + +The `Request` object in `djangorestframework` extends the standard `HttpRequest`, adding support for parsing multiple content types, allowing browser-based `PUT`, `DELETE` and other methods, and adding flexible per-request authentication. + +method +------ + +`request.method` returns the uppercased string representation of the request's HTTP method. + +Browser-based `PUT`, `DELETE` and other requests are supported, and can be made by using a hidden form field named `_method` in a regular `POST` form. + + + +content_type +------------ + +`request.content`, returns a string object representing the mimetype of the HTTP request's body, if one exists. + + + +DATA +---- + +`request.DATA` returns the parsed content of the request body. This is similar to the standard `HttpRequest.POST` attribute except that: + +1. It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests. +2. It supports parsing multiple content types, rather than just form data. For example you can handle incoming json data in the same way that you handle incoming form data. + +FILES +----- + +`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing that is used for `request.DATA`. + +This allows you to support file uploads from multiple content-types. For example you can write a parser that supports `POST`ing the raw content of a file, instead of using form-encoded file uploads. + +user +---- + +`request.user` returns a `django.contrib.auth.models.User` instance. + +auth +---- + +`request.auth` returns any additional authentication context that may not be contained in `request.user`. The exact behavior of `request.auth` depends on what authentication has been set in `request.authentication`. For many types of authentication this will simply be `None`, but it may also be an object representing a permission scope, an expiry time, or any other information that might be contained in a token-based authentication scheme. + +parsers +------- + +`request.parsers` should be set to a list of `Parser` instances that can be used to parse the content of the request body. + +`request.parsers` may no longer be altered once `request.DATA`, `request.FILES` or `request.POST` have been accessed. + +If you're using the `djangorestframework.views.View` class... **[TODO]** + +stream +------ + +`request.stream` returns a stream representing the content of the request body. + +You will not typically need to access `request.stream`, unless you're writing a `Parser` class. + +authentication +-------------- + +`request.authentication` should be set to a list of `Authentication` instances that can be used to authenticate the request. + +`request.authentication` may no longer be altered once `request.user` or `request.auth` have been accessed. + +If you're using the `djangorestframework.views.View` class... **[TODO]** + +[1]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 46a671494..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Documentation requires Django & Sphinx, and their dependencies... - -Django>=1.2.4 -Jinja2==2.5.5 -Pygments==1.4 -Sphinx==1.0.7 -docutils==0.7 -wsgiref==0.1.2 diff --git a/docs/response.md b/docs/response.md new file mode 100644 index 000000000..d77c9a0df --- /dev/null +++ b/docs/response.md @@ -0,0 +1,27 @@ +Responses +========= + +> HTTP has provisions for several mechanisms for "content negotiation" -- the process of selecting the best representation for a given response when there are multiple representations available. -- RFC 2616, Fielding et al. + +> Unlike basic HttpResponse objects, TemplateResponse objects retain the details of the context that was provided by the view to compute the response. The final output of the response is not computed until it is needed, later in the response process. -- Django documentation. + +Django REST framework supports HTTP content negotiation by providing a `Response` class which allows you to return content that can be rendered into multiple content types, depending on the client request. + +The `Response` class subclasses Django's `TemplateResponse`. It works by allowing you to specify a serializer and a number of different renderers. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. + +There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it does provide a better interface for returning Web API responses. + +Response(content, status, headers=None, serializer=None, renderers=None, format=None) +------------------------------------------------------------------------------------- + +serializer +---------- + +renderers +--------- + +view +---- + +ImmediateResponse(...) +---------------------- \ No newline at end of file diff --git a/docs/serializers.md b/docs/serializers.md new file mode 100644 index 000000000..23e37f405 --- /dev/null +++ b/docs/serializers.md @@ -0,0 +1,47 @@ +Serializers +=========== + +> Expanding the usefulness of the serializers is something that we would +like to address. However, it's not a trivial problem, and it +will take some serious design work. Any offers to help out in this +area would be gratefully accepted. + - Russell Keith-Magee, [Django users group][1] + +Serializers provide a way of filtering the content of responses, prior to the response being rendered. + +They also allow us to use complex data such as querysets and model instances for the content of our responses, and convert that data into native python datatypes that can then be easily rendered into `JSON`, `XML` or whatever. + +REST framework includes a default `Serializer` class which gives you a powerful, generic way to control the output of your responses, but you can also write custom serializers for your data, or create other generic serialization strategies to suit the needs of your API. + +BaseSerializer +-------------- + +This is the base class for all serializers. If you want to provide your own custom serialization, override this class. + +.serialize() +------------ + +Serializer +---------- + +This is the default serializer. + +fields +------ + +include +------- + +exclude +------- + +rename +------ + +related_serializer +------------------ + +depth +----- + +[1]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion diff --git a/docs/status.md b/docs/status.md new file mode 100644 index 000000000..ca866cada --- /dev/null +++ b/docs/status.md @@ -0,0 +1,17 @@ +Status Codes +============ + +> 418 I'm a teapot - Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout. + - RFC 2324 + +REST framework provides a ... +These are simply ... + + from djangorestframework import status + + def view(self): + return Response(status=status.HTTP_404_NOT_FOUND) + +For more information see [RFC 2616](1). + +[1]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html \ No newline at end of file diff --git a/docs/templates/layout.html b/docs/templates/layout.html deleted file mode 100644 index a59645f2a..000000000 --- a/docs/templates/layout.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "!layout.html" %} - -{%- if not embedded and docstitle %} - {%- set titleprefix = docstitle|e + " - "|safe %} -{%- else %} - {%- set titleprefix = "" %} -{%- endif %} - -{% block htmltitle %}{% if pagename == 'index' %}Django REST framework{% else %}{{ titleprefix }}{{ title|striptags|e }}{% endif %}{% endblock %} - -{% block extrahead %} -{{ super() }} - -{% endblock %} -{% block footer %} - diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md new file mode 100644 index 000000000..55a9f679f --- /dev/null +++ b/docs/tutorial/1-serialization.md @@ -0,0 +1,236 @@ +# Tutorial 1: Serialization + +## Introduction + +This tutorial will walk you through the building blocks that make up REST framework. It'll take a little while to get through, but it'll give you a comprehensive understanding of how everything fits together. + +## Getting started + +To get started, let's create a new project to work with. + + django-admin.py startproject tutorial + cd tutorial + +Once that's done we can create an app that we'll use to create a simple Web API. + + python manage.py startapp blog + +The simplest way to get up and running will probably be to use an `sqlite3` database for the tutorial. Edit the `tutorial/settings.py` file, and set the default database `"ENGINE"` to `"sqlite3"`, and `"NAME"` to `"tmp.db"`. + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'tmp.db', + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + } + } + +We'll also need to add our new `blog` app and the `djangorestframework` app to `INSTALLED_APPS`. + + INSTALLED_APPS = ( + ... + 'djangorestframework', + 'blog' + ) + +We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our blog views. + + urlpatterns = patterns('', + url(r'^', include('blog.urls')), + ) + +Okay, we're ready to roll. + +## Creating a model to work with + +For the purposes of this tutorial we're going to start by creating a simple `Comment` model that is used to store comments against a blog post. Go ahead and edit the `blog` app's `models.py` file. + + from django.db import models + + class Comment(models.Model): + email = models.EmailField() + content = models.CharField(max_length=200) + created = models.DateTimeField(auto_now_add=True) + +Don't forget to sync the database for the first time. + + python manage.py syncdb + +## Creating a Serializer class + +We're going to create a simple Web API that we can use to edit these comment objects with. The first thing we need is a way of serializing and deserializing the objects into representations such as `json`. We do this by declaring serializers, that work very similarly to Django's forms. Create a file in the project named `serializers.py` and add the following. + + from blog import models + from djangorestframework import serializers + + + class CommentSerializer(serializers.Serializer): + email = serializers.EmailField() + content = serializers.CharField(max_length=200) + created = serializers.DateTimeField() + + def restore_object(self, attrs, instance=None): + """ + Create or update a new comment instance. + """ + if instance: + instance.email = attrs['email'] + instance.content = attrs['content'] + instance.created = attrs['created'] + return instance + return models.Comment(**attrs) + +The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. + +We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit. + +## Working with Serializers + +Before we go any further we'll familiarise ourselves with using our new Serializer class. Let's drop into the Django shell. + + python manage.py shell + +Okay, once we've got a few imports out of the way, we'd better create a few comments to work with. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework.renderers import JSONRenderer + from djangorestframework.parsers import JSONParser + + c1 = Comment(email='leila@example.com', content='nothing to say') + c2 = Comment(email='tom@example.com', content='foo bar') + c3 = Comment(email='anna@example.com', content='LOLZ!') + c1.save() + c2.save() + c3.save() + +We've now got a few comment instances to play with. Let's take a look at serializing one of those instances. + + serializer = CommentSerializer(instance=c1) + serializer.data + # {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)} + +At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`. + + stream = JSONRenderer().render(serializer.data) + stream + # '{"email": "leila@example.com", "content": "nothing to say", "created": "2012-08-22T16:20:09.822"}' + +Deserialization is similar. First we parse a stream into python native datatypes... + + data = JSONParser().parse(stream) + +...then we restore those native datatypes into to a fully populated object instance. + + serializer = CommentSerializer(data) + serializer.is_valid() + # True + serializer.object + # + +Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer. + +## Writing regular Django views using our Serializers + +Let's see how we can write some API views using our new Serializer class. +We'll start off by creating a subclass of HttpResponse that we can use to render any data we return into `json`. + +Edit the `blog/views.py` file, and add the following. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework.renderers import JSONRenderer + from djangorestframework.parsers import JSONParser + from django.http import HttpResponse + + + class JSONResponse(HttpResponse): + """ + An HttpResponse that renders it's content into JSON. + """ + + def __init__(self, data, **kwargs): + content = JSONRenderer().render(data) + kwargs['content_type'] = 'application/json' + super(JSONResponse, self).__init__(content, **kwargs) + + +The root of our API is going to be a view that supports listing all the existing comments, or creating a new comment. + + def comment_root(request): + """ + List all comments, or create a new comment. + """ + if request.method == 'GET': + comments = Comment.objects.all() + serializer = CommentSerializer(instance=comments) + return JSONResponse(serializer.data) + + elif request.method == 'POST': + data = JSONParser().parse(request) + serializer = CommentSerializer(data) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return JSONResponse(serializer.data, status=201) + else: + return JSONResponse(serializer.error_data, status=400) + +We'll also need a view which corrosponds to an individual comment, and can be used to retrieve, update or delete the comment. + + def comment_instance(request, pk): + """ + Retrieve, update or delete a comment instance. + """ + try: + comment = Comment.objects.get(pk=pk) + except Comment.DoesNotExist: + return HttpResponse(status=404) + + if request.method == 'GET': + serializer = CommentSerializer(instance=comment) + return JSONResponse(serializer.data) + + elif request.method == 'PUT': + data = JSONParser().parse(request) + serializer = CommentSerializer(data, instance=comment) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return JSONResponse(serializer.data) + else: + return JSONResponse(serializer.error_data, status=400) + + elif request.method == 'DELETE': + comment.delete() + return HttpResponse(status=204) + +Finally we need to wire these views up, in the `tutorial/urls.py` file. + + from django.conf.urls import patterns, url + + urlpatterns = patterns('blog.views', + url(r'^$', 'comment_root'), + url(r'^(?P[0-9]+)$', 'comment_instance') + ) + +It's worth noting that there's a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now. + +## Testing our first attempt at a Web API + +**TODO: Describe using runserver and making example requests from console** + +**TODO: Describe opening in a web browser and viewing json output** + +## Where are we now + +We're doing okay so far, we've got a serialization API that feels pretty similar to Django's Forms API, and some regular Django views. + +Our API views don't do anything particularly special at the moment, beyond serve `json` responses, and there's some error handling edge cases we'd still like to clean up, but it's a functioning Web API. + +We'll see how we can start to improve things in [part 2 of the tutorial][1]. + +[1]: 2-requests-and-responses.md \ No newline at end of file diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md new file mode 100644 index 000000000..2bb6c20eb --- /dev/null +++ b/docs/tutorial/2-requests-and-responses.md @@ -0,0 +1,137 @@ +# Tutorial 2: Request and Response objects + +From this point we're going to really start covering the core of REST framework. +Let's introduce a couple of essential building blocks. + +## Request objects + +REST framework intoduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. + + request.POST # Only handles form data. Only works for 'POST' method. + request.DATA # Handles arbitrary data. Works any HTTP request with content. + +## Response objects + +REST framework also introduces a `Response` object, which is a type of `TemplateResponse` that takes unrendered content and uses content negotiation to determine the correct content type to return to the client. + + return Response(data) # Renders to content type as requested by the client. + +## Status codes + +Using numeric HTTP status codes in your views doesn't always make for obvious reading, and it's easy to not notice if you get an error code wrong. REST framework provides more explicit identifiers for each status code, such as `HTTP_400_BAD_REQUEST` in the `status` module. It's a good idea to use these throughout rather than using numeric identifiers. + +## Wrapping API views + +REST framework provides two wrappers you can use to write API views. + +1. The `@api_view` decorator for working with function based views. +2. The `APIView` class for working with class based views. + +These wrappers provide a few bits of functionality such as making sure you recieve `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed. + +The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.DATA` with malformed input. + + +## Pulling it all together + +Okay, let's go ahead and start using these new components to write a few views. + + from djangorestframework.decorators import api_view + from djangorestframework.status import * + + @api_view(allow=['GET', 'POST']) + def comment_root(request): + """ + List all comments, or create a new comment. + """ + if request.method == 'GET': + comments = Comment.objects.all() + serializer = CommentSerializer(instance=comments) + return Response(serializer.data) + + elif request.method == 'POST': + serializer = CommentSerializer(request.DATA) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return Response(serializer.data, status=HTTP_201_CREATED) + else: + return Response(serializer.error_data, status=HTTP_400_BAD_REQUEST) + + +Our instance view is an improvement over the previous example. It's slightly more concise, and the code now feels very similar to if we were working with the Forms API. + + @api_view(allow=['GET', 'PUT', 'DELETE']) + def comment_instance(request, pk): + """ + Retrieve, update or delete a comment instance. + """ + try: + comment = Comment.objects.get(pk=pk) + except Comment.DoesNotExist: + return Response(status=HTTP_404_NOT_FOUND) + + if request.method == 'GET': + serializer = CommentSerializer(instance=comment) + return Response(serializer.data) + + elif request.method == 'PUT': + serializer = CommentSerializer(request.DATA, instance=comment) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return Response(serializer.data) + else: + return Response(serializer.error_data, status=HTTP_400_BAD_REQUEST) + + elif request.method == 'DELETE': + comment.delete() + return Response(status=HTTP_204_NO_CONTENT) + +This should all feel very familiar - it looks a lot like working with forms in regular Django views. + +Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.DATA` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. + +## Adding optional format suffixes to our URLs + +To take advantage of that, let's add support for format suffixes to our API endpoints, so that we can use URLs that explicitly refer to a given format. That means our API will be able to handle URLs such as [http://example.com/api/items/4.json][1]. + +Start by adding a `format` keyword argument to both of the views, like so. + + def comment_root(request, format=None): + +and + + def comment_instance(request, pk, format=None): + +Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs. + + from djangorestframework.urlpatterns import format_suffix_patterns + + urlpatterns = patterns('blogpost.views', + url(r'^$', 'comment_root'), + url(r'^(?P[0-9]+)$', 'comment_instance') + ) + + urlpatterns = format_suffix_patterns(urlpatterns) + +We don't necessarily need to add these extra url patterns in, but it gives us a simple, clean way of refering to a specific format. + +## How's it looking? + +Go ahead and test the API from the command line, as we did in [tutorial part 1][2]. Everything is working pretty similarly, although we've got some nicer error handling if we send invalid requests. + +**TODO: Describe using accept headers, content-type headers, and format suffixed URLs** + +Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/][3]. + +**TODO: Describe browseable API awesomeness** + +## What's next? + +In [tutorial part 3][4], we'll start using class based views, and see how generic views reduce the amount of code we need to write. + +[1]: http://example.com/api/items/4.json +[2]: 1-serialization.md +[3]: http://127.0.0.1:8000/ +[4]: 3-class-based-views.md \ No newline at end of file diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md new file mode 100644 index 000000000..e56c78479 --- /dev/null +++ b/docs/tutorial/3-class-based-views.md @@ -0,0 +1,137 @@ +# Tutorial 3: Using Class Based Views + +We can also write our API views using class based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][1]. + +## Rewriting our API using class based views + +We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring. + + from blog.models import Comment + from blog.serializers import ComentSerializer + from django.http import Http404 + from djangorestframework.views import APIView + from djangorestframework.response import Response + from djangorestframework.status import * + + class CommentRoot(views.APIView): + """ + List all comments, or create a new comment. + """ + def get(self, request, format=None): + comments = Comment.objects.all() + serializer = ComentSerializer(instance=comments) + return Response(serializer.data) + + def post(self, request, format=None) + serializer = ComentSerializer(request.DATA) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return Response(serializer.serialized, status=HTTP_201_CREATED) + else: + return Response(serializer.serialized_errors, status=HTTP_400_BAD_REQUEST) + +So far, so good. It looks pretty similar to the previous case, but we've got better seperation between the different HTTP methods. We'll also need to update the instance view. + + class CommentInstance(views.APIView): + """ + Retrieve, update or delete a comment instance. + """ + + def get_object(self, pk): + try: + return Poll.objects.get(pk=pk) + except Poll.DoesNotExist: + raise Http404 + + def get(self, request, pk, format=None): + comment = self.get_object(pk) + serializer = CommentSerializer(instance=comment) + return Response(serializer.data) + + def put(self, request, pk, format=None): + comment = self.get_object(pk) + serializer = CommentSerializer(request.DATA, instance=comment) + if serializer.is_valid(): + comment = serializer.deserialized + comment.save() + return Response(serializer.data) + else: + return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) + + def delete(self, request, pk, format=None): + comment = self.get_object(pk) + comment.delete() + return Response(status=HTTP_204_NO_CONTENT) + +That's looking good. Again, it's still pretty similar to the function based view right now. + +Since we're now working with class based views, rather than function based views, we'll also need to update our urlconf slightly. + + from blogpost import views + from djangorestframework.urlpatterns import format_suffix_patterns + + urlpatterns = patterns('', + url(r'^$', views.CommentRoot.as_view()), + url(r'^(?P[0-9]+)$', views.CommentInstance.as_view()) + ) + + urlpatterns = format_suffix_patterns(urlpatterns) + +Okay, we're done. If you run the development server everything should be working just as before. + +## Using mixins + +One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour. + +The create/retrieve/update/delete operations that we've been using so far is going to be pretty simliar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes. + +We can compose those mixin classes, to recreate our existing API behaviour with less code. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework import mixins, views + + class CommentRoot(mixins.ListModelQuerysetMixin, + mixins.CreateModelInstanceMixin, + views.BaseRootAPIView): + model = Comment + serializer_class = CommentSerializer + + get = list + post = create + + class CommentInstance(mixins.RetrieveModelInstanceMixin, + mixins.UpdateModelInstanceMixin, + mixins.DestroyModelInstanceMixin, + views.BaseInstanceAPIView): + model = Comment + serializer_class = CommentSerializer + + get = retrieve + put = update + delete = destroy + +## Reusing generic class based views + +That's a lot less code than before, but we can go one step further still. REST framework also provides a set of already mixed-in views. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework import views + + class CommentRoot(views.RootAPIView): + model = Comment + serializer_class = CommentSerializer + + class CommentInstance(views.InstanceAPIView): + model = Comment + serializer_class = CommentSerializer + +Wow, that's pretty concise. We've got a huge amount for free, and our code looks like +good, clean, idomatic Django. + +Next we'll move onto [part 4 of the tutorial][2], where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects. + +[1]: http://en.wikipedia.org/wiki/Don't_repeat_yourself +[2]: 4-authentication-permissions-and-throttling.md diff --git a/docs/tutorial/4-authentication-permissions-and-throttling.md b/docs/tutorial/4-authentication-permissions-and-throttling.md new file mode 100644 index 000000000..5c37ae13e --- /dev/null +++ b/docs/tutorial/4-authentication-permissions-and-throttling.md @@ -0,0 +1,3 @@ +[part 5][5] + +[5]: 5-relationships-and-hyperlinked-apis.md \ No newline at end of file diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md new file mode 100644 index 000000000..3d9598d7d --- /dev/null +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -0,0 +1,9 @@ +**TODO** + +* Create BlogPost model +* Demonstrate nested relationships +* Demonstrate and describe hyperlinked relationships + +[part 6][1] + +[1]: 6-resource-orientated-projects.md diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md new file mode 100644 index 000000000..ce51cce5d --- /dev/null +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -0,0 +1,49 @@ +serializers.py + + class BlogPostSerializer(URLModelSerializer): + class Meta: + model = BlogPost + + class CommentSerializer(URLModelSerializer): + class Meta: + model = Comment + +resources.py + + class BlogPostResource(ModelResource): + serializer_class = BlogPostSerializer + model = BlogPost + permissions = [AdminOrAnonReadonly()] + throttles = [AnonThrottle(rate='5/min')] + + class CommentResource(ModelResource): + serializer_class = CommentSerializer + model = Comment + permissions = [AdminOrAnonReadonly()] + throttles = [AnonThrottle(rate='5/min')] + +Now that we're using Resources rather than Views, we don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls are handled automatically. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file. + + from blog import resources + from djangorestframework.routers import DefaultRouter + + router = DefaultRouter() + router.register(resources.BlogPostResource) + router.register(resources.CommentResource) + urlpatterns = router.urlpatterns + +## Trade-offs between views vs resources. + +Writing resource-orientated code can be a good thing. It helps ensure that URL conventions will be consistent across your APIs, and minimises the amount of code you need to write. + +The trade-off is that the behaviour is less explict. It can be more difficult to determine what code path is being followed, or where to override some behaviour. + +## Onwards and upwards. + +We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: + +* Contribute on GitHub by reviewing issues, and submitting issues or pull requests. +* Join the REST framework group, and help build the community. +* Follow me on Twitter and say hi. + +Now go build something great. \ No newline at end of file diff --git a/docs/urls.md b/docs/urls.md new file mode 100644 index 000000000..1828dd682 --- /dev/null +++ b/docs/urls.md @@ -0,0 +1,42 @@ +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, eg. "http://example.com/foobar", rather than returning relative URIs, eg. "/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 use to easily do things like markup HTML representations with hyperlinks. + +Django REST framework provides two utility functions to make it more simple 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 it's output for you, which makes browsing the API much easier. + +reverse(viewname, request, ...) +------------------------------- + +Has the same behavior as [`django.core.urlresolvers.reverse`](1), except that it returns a fully qualified URL, using the request to determine the host and port. + + from djangorestframework.utils import reverse + from djangorestframework.views import View + + class MyView(View): + def get(self, request): + context = { + ... + 'url': reverse('year-summary', request, args=[1945]) + } + return Response(context) + +reverse_lazy(viewname, request, ...) +------------------------------------ + +Has the same behavior as [`django.core.urlresolvers.reverse_lazy`](2), except that it returns a fully qualified URL, using the request to determine the host and port. + +[1]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse +[1]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy \ No newline at end of file diff --git a/docs/views.md b/docs/views.md new file mode 100644 index 000000000..d227339e7 --- /dev/null +++ b/docs/views.md @@ -0,0 +1,43 @@ +Views +===== + +REST framework provides a simple `View` class, built on Django's `django.generics.views.View`. The `View` class ensures five main things: + +1. Any requests inside the view will become `Request` instances. +2. `Request` instances will have their `renderers` and `authentication` attributes automatically set. +3. `Response` instances will have their `parsers` and `serializer` attributes automatically set. +4. `ImmediateResponse` exceptions will be caught and returned as regular responses. +5. Any permissions provided will be checked prior to passing the request to a handler method. + +Additionally there are a some minor extras, such as providing a default `options` handler, setting some common headers on the response prior to return, and providing the useful `initial()` and `final()` hooks. + +View +---- + +.get(), .post(), .put(), .delete() etc... +----------------------------------------- + +.initial(request, *args, **kwargs) +---------------------------------- + +.final(request, response, *args, **kwargs) +------------------------------------------ + +.parsers +-------- + +.renderers +---------- + +.serializer +----------- + +.authentication +--------------- + +.permissions +------------ + +.headers +-------- + diff --git a/requirements.txt b/requirements.txt index 56926c0f4..120cbbe50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,2 @@ -# We need Django. Duh. -# coverage isn't strictly a requirement, but it's useful. - -Django>=1.2 -coverage>=3.4 -URLObject>=0.6.0 +Django>=1.3 +URLObject>=2.0.0