aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore16
-rw-r--r--.travis.yml21
-rw-r--r--AUTHORS48
-rw-r--r--LICENSE22
-rw-r--r--MANIFEST.in7
-rw-r--r--README.md101
-rw-r--r--README.rst90
-rw-r--r--djangorestframework/authentication.py101
-rw-r--r--djangorestframework/mixins.py736
-rw-r--r--djangorestframework/parsers.py258
-rw-r--r--djangorestframework/permissions.py267
-rw-r--r--djangorestframework/renderers.py417
-rw-r--r--djangorestframework/resources.py345
-rw-r--r--djangorestframework/response.py44
-rw-r--r--djangorestframework/serializer.py300
-rw-r--r--djangorestframework/static/djangorestframework/css/style.css1209
-rw-r--r--djangorestframework/templates/djangorestframework/api.txt8
-rw-r--r--djangorestframework/templates/djangorestframework/base.html150
-rw-r--r--djangorestframework/templates/djangorestframework/login.html45
-rw-r--r--djangorestframework/templatetags/add_query_param.py10
-rw-r--r--djangorestframework/tests/__init__.py13
-rw-r--r--djangorestframework/tests/accept.py86
-rw-r--r--djangorestframework/tests/content.py233
-rw-r--r--djangorestframework/tests/files.py32
-rw-r--r--djangorestframework/tests/methods.py32
-rw-r--r--djangorestframework/tests/mixins.py291
-rw-r--r--djangorestframework/tests/models.py28
-rw-r--r--djangorestframework/tests/modelviews.py90
-rw-r--r--djangorestframework/tests/oauthentication.py212
-rw-r--r--djangorestframework/tests/package.py11
-rw-r--r--djangorestframework/tests/response.py19
-rw-r--r--djangorestframework/tests/reverse.py52
-rw-r--r--djangorestframework/tests/serializer.py160
-rw-r--r--djangorestframework/tests/validators.py325
-rw-r--r--djangorestframework/tests/views.py137
-rw-r--r--djangorestframework/urls.py9
-rw-r--r--djangorestframework/views.py292
-rw-r--r--docs/api-guide/authentication.md126
-rw-r--r--docs/api-guide/content-negotiation.md66
-rw-r--r--docs/api-guide/exceptions.md88
-rw-r--r--docs/api-guide/fields.md278
-rw-r--r--docs/api-guide/format-suffixes.md61
-rw-r--r--docs/api-guide/generic-views.md187
-rw-r--r--docs/api-guide/pagination.md125
-rw-r--r--docs/api-guide/parsers.md161
-rw-r--r--docs/api-guide/permissions.md118
-rw-r--r--docs/api-guide/renderers.md267
-rw-r--r--docs/api-guide/requests.md128
-rw-r--r--docs/api-guide/responses.md94
-rw-r--r--docs/api-guide/reverse.md55
-rw-r--r--docs/api-guide/serializers.md276
-rw-r--r--docs/api-guide/settings.md153
-rw-r--r--docs/api-guide/status-codes.md95
-rw-r--r--docs/api-guide/throttling.md157
-rw-r--r--docs/api-guide/views.md168
-rw-r--r--docs/check_sphinx.py9
-rw-r--r--docs/conf.py228
-rw-r--r--docs/contents.rst10
-rwxr-xr-xdocs/css/bootstrap-responsive.css1058
-rwxr-xr-xdocs/css/bootstrap.css5774
-rw-r--r--docs/css/default.css268
-rw-r--r--docs/css/prettify.css30
-rw-r--r--docs/examples.rst23
-rw-r--r--docs/examples/blogpost.rst37
-rw-r--r--docs/examples/modelviews.rst56
-rw-r--r--docs/examples/objectstore.rst17
-rw-r--r--docs/examples/permissions.rst66
-rw-r--r--docs/examples/pygments.rst89
-rw-r--r--docs/examples/sandbox.rst12
-rw-r--r--docs/examples/views.rst56
-rw-r--r--docs/howto.rst8
-rw-r--r--docs/howto/alternativeframeworks.rst35
-rw-r--r--docs/howto/mixin.rst30
-rw-r--r--docs/howto/reverse.rst50
-rw-r--r--docs/howto/setup.rst73
-rw-r--r--docs/howto/usingcurl.rst30
-rw-r--r--docs/howto/usingurllib2.rst39
-rw-r--r--docs/img/favicon.icobin0 -> 2238 bytes
-rw-r--r--docs/img/glyphicons-halflings-white.pngbin0 -> 8777 bytes
-rw-r--r--docs/img/glyphicons-halflings.pngbin0 -> 12799 bytes
-rw-r--r--docs/img/grid.pngbin0 -> 1458 bytes
-rw-r--r--docs/img/quickstart.pngbin0 -> 39050 bytes
-rw-r--r--docs/index.md208
-rw-r--r--docs/index.rst11
-rwxr-xr-xdocs/js/bootstrap-2.1.1-min.js7
-rw-r--r--docs/js/jquery-1.8.1-min.js2
-rw-r--r--docs/js/prettify-1.0.js28
-rw-r--r--docs/library.rst8
-rw-r--r--docs/library/authentication.rst5
-rw-r--r--docs/library/compat.rst5
-rw-r--r--docs/library/mixins.rst5
-rw-r--r--docs/library/parsers.rst5
-rw-r--r--docs/library/permissions.rst5
-rw-r--r--docs/library/renderers.rst10
-rw-r--r--docs/library/resource.rst5
-rw-r--r--docs/library/response.rst5
-rw-r--r--docs/library/reverse.rst5
-rw-r--r--docs/library/serializer.rst5
-rw-r--r--docs/library/status.rst5
-rw-r--r--docs/library/views.rst5
-rw-r--r--docs/requirements.txt9
-rw-r--r--docs/template.html163
-rw-r--r--docs/templates/layout.html28
-rw-r--r--docs/topics/browsable-api.md102
-rw-r--r--docs/topics/browser-enhancements.md64
-rw-r--r--docs/topics/contributing.md15
-rw-r--r--docs/topics/credits.md139
-rw-r--r--docs/topics/csrf.md12
-rw-r--r--docs/topics/migration.md89
-rw-r--r--docs/topics/release-notes.md (renamed from CHANGELOG.rst)53
-rw-r--r--docs/topics/rest-framework-2-announcement.md100
-rw-r--r--docs/topics/rest-hypermedia-hateoas.md53
-rw-r--r--docs/tutorial/1-serialization.md315
-rw-r--r--docs/tutorial/2-requests-and-responses.md150
-rw-r--r--docs/tutorial/3-class-based-views.md150
-rw-r--r--docs/tutorial/4-authentication-and-permissions.md189
-rw-r--r--docs/tutorial/5-relationships-and-hyperlinked-apis.md173
-rw-r--r--docs/tutorial/quickstart.md172
-rw-r--r--examples/.epio-app1
-rw-r--r--examples/blogpost/models.py41
-rw-r--r--examples/blogpost/resources.py36
-rw-r--r--examples/blogpost/tests.py210
-rw-r--r--examples/blogpost/urls.py11
-rw-r--r--examples/epio.ini62
-rwxr-xr-xexamples/manage.py11
-rw-r--r--examples/media/objectstore/.keep1
-rw-r--r--examples/media/pygments/.keep1
-rw-r--r--examples/mixin/urls.py25
-rw-r--r--examples/modelresourceexample/models.py18
-rw-r--r--examples/modelresourceexample/resources.py14
-rw-r--r--examples/modelresourceexample/urls.py11
-rw-r--r--examples/objectstore/__init__.py0
-rw-r--r--examples/objectstore/urls.py7
-rw-r--r--examples/objectstore/views.py106
-rw-r--r--examples/permissionsexample/__init__.py0
-rw-r--r--examples/permissionsexample/fixtures/initial_data.json18
-rw-r--r--examples/permissionsexample/models.py1
-rw-r--r--examples/permissionsexample/urls.py8
-rw-r--r--examples/permissionsexample/views.py52
-rw-r--r--examples/pygments_api/__init__.py0
-rw-r--r--examples/pygments_api/forms.py28
-rw-r--r--examples/pygments_api/models.py1
-rw-r--r--examples/pygments_api/tests.py49
-rw-r--r--examples/pygments_api/urls.py7
-rw-r--r--examples/pygments_api/views.py115
-rw-r--r--examples/requirements-epio.txt3
-rw-r--r--examples/requirements.txt7
-rw-r--r--examples/resourceexample/__init__.py0
-rw-r--r--examples/resourceexample/forms.py6
-rw-r--r--examples/resourceexample/urls.py7
-rw-r--r--examples/resourceexample/views.py48
-rw-r--r--examples/runtests.py44
-rw-r--r--examples/sandbox/__init__.py0
-rw-r--r--examples/sandbox/views.py37
-rw-r--r--examples/settings.py116
-rw-r--r--examples/urls.py21
-rwxr-xr-xmkdocs.py82
-rw-r--r--optionals.txt2
-rw-r--r--requirements.txt7
-rw-r--r--rest_framework/__init__.py (renamed from djangorestframework/__init__.py)2
-rw-r--r--rest_framework/authentication.py125
-rw-r--r--rest_framework/authtoken/__init__.py (renamed from djangorestframework/runtests/__init__.py)0
-rw-r--r--rest_framework/authtoken/migrations/0001_initial.py70
-rw-r--r--rest_framework/authtoken/migrations/__init__.py (renamed from djangorestframework/templatetags/__init__.py)0
-rw-r--r--rest_framework/authtoken/models.py25
-rw-r--r--rest_framework/authtoken/views.py (renamed from examples/__init__.py)0
-rw-r--r--rest_framework/compat.py (renamed from djangorestframework/compat.py)256
-rw-r--r--rest_framework/decorators.py88
-rw-r--r--rest_framework/exceptions.py78
-rw-r--r--rest_framework/fields.py695
-rw-r--r--rest_framework/generics.py187
-rw-r--r--rest_framework/mixins.py116
-rw-r--r--rest_framework/models.py (renamed from djangorestframework/models.py)0
-rw-r--r--rest_framework/negotiation.py82
-rw-r--r--rest_framework/pagination.py80
-rw-r--r--rest_framework/parsers.py189
-rw-r--r--rest_framework/permissions.py114
-rw-r--r--rest_framework/renderers.py452
-rw-r--r--rest_framework/request.py317
-rw-r--r--rest_framework/response.py47
-rw-r--r--rest_framework/reverse.py (renamed from djangorestframework/reverse.py)8
-rw-r--r--rest_framework/runtests/__init__.py (renamed from examples/blogpost/__init__.py)0
-rwxr-xr-x[-rw-r--r--]rest_framework/runtests/runcoverage.py (renamed from djangorestframework/runtests/runcoverage.py)22
-rwxr-xr-x[-rw-r--r--]rest_framework/runtests/runtests.py (renamed from djangorestframework/runtests/runtests.py)11
-rw-r--r--rest_framework/runtests/settings.py (renamed from djangorestframework/runtests/settings.py)13
-rw-r--r--rest_framework/runtests/urls.py (renamed from djangorestframework/runtests/urls.py)0
-rw-r--r--rest_framework/serializers.py513
-rw-r--r--rest_framework/settings.py149
-rw-r--r--rest_framework/static/rest_framework/css/bootstrap-tweaks.css22
-rw-r--r--rest_framework/static/rest_framework/css/bootstrap.min.css841
-rw-r--r--rest_framework/static/rest_framework/css/default.css170
-rw-r--r--rest_framework/static/rest_framework/css/prettify.css30
-rw-r--r--rest_framework/static/rest_framework/img/glyphicons-halflings-white.pngbin0 -> 8777 bytes
-rw-r--r--rest_framework/static/rest_framework/img/glyphicons-halflings.pngbin0 -> 12762 bytes
-rw-r--r--rest_framework/static/rest_framework/img/grid.pngbin0 -> 1458 bytes
-rw-r--r--rest_framework/static/rest_framework/js/bootstrap.min.js7
-rw-r--r--rest_framework/static/rest_framework/js/default.js5
-rw-r--r--rest_framework/static/rest_framework/js/jquery-1.8.1-min.js2
-rw-r--r--rest_framework/static/rest_framework/js/prettify-min.js28
-rw-r--r--rest_framework/status.py (renamed from djangorestframework/status.py)8
-rw-r--r--rest_framework/templates/rest_framework/api.html (renamed from djangorestframework/templates/djangorestframework/api.html)4
-rw-r--r--rest_framework/templates/rest_framework/base.html204
-rw-r--r--rest_framework/templates/rest_framework/login.html53
-rw-r--r--rest_framework/templatetags/__init__.py (renamed from examples/mixin/__init__.py)0
-rw-r--r--rest_framework/templatetags/rest_framework.py (renamed from djangorestframework/templatetags/urlize_quoted_links.py)126
-rw-r--r--rest_framework/tests/__init__.py (renamed from examples/modelresourceexample/__init__.py)0
-rw-r--r--rest_framework/tests/authentication.py (renamed from djangorestframework/tests/authentication.py)79
-rw-r--r--rest_framework/tests/breadcrumbs.py (renamed from djangorestframework/tests/breadcrumbs.py)21
-rw-r--r--rest_framework/tests/decorators.py128
-rw-r--r--rest_framework/tests/description.py (renamed from djangorestframework/tests/description.py)18
-rw-r--r--rest_framework/tests/files.py34
-rw-r--r--rest_framework/tests/genericrelations.py33
-rw-r--r--rest_framework/tests/generics.py303
-rw-r--r--rest_framework/tests/htmlrenderer.py50
-rw-r--r--rest_framework/tests/hyperlinkedserializers.py168
-rw-r--r--rest_framework/tests/models.py128
-rw-r--r--rest_framework/tests/modelviews.py90
-rw-r--r--rest_framework/tests/negotiation.py37
-rw-r--r--rest_framework/tests/pagination.py87
-rw-r--r--rest_framework/tests/parsers.py (renamed from djangorestframework/tests/parsers.py)54
-rw-r--r--rest_framework/tests/renderers.py (renamed from djangorestframework/tests/renderers.py)234
-rw-r--r--rest_framework/tests/request.py278
-rw-r--r--rest_framework/tests/response.py182
-rw-r--r--rest_framework/tests/reverse.py26
-rw-r--r--rest_framework/tests/serializer.py500
-rw-r--r--rest_framework/tests/status.py (renamed from djangorestframework/tests/status.py)2
-rw-r--r--rest_framework/tests/testcases.py (renamed from djangorestframework/tests/testcases.py)2
-rw-r--r--rest_framework/tests/tests.py13
-rw-r--r--rest_framework/tests/throttling.py (renamed from djangorestframework/tests/throttling.py)90
-rw-r--r--rest_framework/tests/validators.py329
-rw-r--r--rest_framework/tests/views.py97
-rw-r--r--rest_framework/throttling.py208
-rw-r--r--rest_framework/urlpatterns.py42
-rw-r--r--rest_framework/urls.py23
-rw-r--r--rest_framework/utils/__init__.py (renamed from djangorestframework/utils/__init__.py)52
-rw-r--r--rest_framework/utils/breadcrumbs.py (renamed from djangorestframework/utils/breadcrumbs.py)4
-rw-r--r--rest_framework/utils/encoders.py91
-rw-r--r--rest_framework/utils/mediatypes.py (renamed from djangorestframework/utils/mediatypes.py)34
-rw-r--r--rest_framework/views.py370
-rwxr-xr-xsetup.py17
-rw-r--r--tox.ini295
241 files changed, 20522 insertions, 9073 deletions
diff --git a/.gitignore b/.gitignore
index 40c68bd5..96186b46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,17 +3,11 @@
*~
.*
-coverage.xml
-env
-docs/build
-html
-htmlcov
-examples/media/pygments/[A-Za-z0-9]*
-examples/media/objectstore/[A-Za-z0-9]*
-build/*
-dist/*
-xmlrunner/*
-djangorestframework.egg-info/*
+html/
+coverage/
+build/
+dist/
+rest_framework.egg-info/
MANIFEST
!.gitignore
diff --git a/.travis.yml b/.travis.yml
index 3ce1ee7b..0e177a95 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,22 +5,13 @@ python:
- "2.7"
env:
- - DJANGO=https://github.com/django/django/zipball/master TESTS='python setup.py test'
- - DJANGO=django==1.4.1 --use-mirrors TESTS='python setup.py test'
- - DJANGO=django==1.3.3 --use-mirrors TESTS='python setup.py test'
+ - DJANGO=https://github.com/django/django/zipball/master
+ - DJANGO=django==1.4.1 --use-mirrors
+ - DJANGO=django==1.3.3 --use-mirrors
-# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install:
- - pip install $DJANGO
- - pip install -e . --use-mirrors
- - pip install -r requirements.txt
+ - pip install $DJANGO
+ - export PYTHONPATH=.
-# command to run tests, e.g. python setup.py test
script:
- - $TESTS
-
-# Examples tests, currently dropped to keep the number of configurations sane
-# - DJANGO=https://github.com/django/django/zipball/master TESTS='python examples/runtests.py'
-# - DJANGO=django==1.4.1 --use-mirrors TESTS='python examples/runtests.py'
-# - DJANGO=django==1.3.3 --use-mirrors TESTS='python examples/runtests.py'
-# - pip install -r examples/requirements.txt \ No newline at end of file
+ - python rest_framework/runtests/runtests.py
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index e0840961..00000000
--- a/AUTHORS
+++ /dev/null
@@ -1,48 +0,0 @@
-Tom Christie <tomchristie> - tom@tomchristie.com, @_tomchristie
-Marko Tibold <markotibold> (Additional thanks for providing & managing the Jenkins CI Server)
-Paul Bagwell <pbgwl>
-Sébastien Piquemal <sebpiq>
-Carmen Wick <cwick>
-Alex Ehlke <aehlke>
-Alen Mujezinovic <flashingpumpkin>
-Carles Barrobés <txels>
-Michael Fötsch <mfoetsch>
-David Larlet <david>
-Andrew Straw <astraw>
-Zeth <zeth>
-Fernando Zunino <fzunino>
-Jens Alm <ulmus>
-Craig Blaszczyk <jakul>
-Garcia Solero <garciasolero>
-Tom Drummond <devioustree>
-Danilo Bargen <gwrtheyrn>
-Andrew McCloud <amccloud>
-Thomas Steinacher <thomasst>
-Meurig Freeman <meurig>
-Anthony Nemitz <anemitz>
-Ewoud Kohl van Wijngaarden <ekohl>
-Michael Ding <yandy>
-Mjumbe Poe <mjumbewu>
-Natim <natim>
-Sebastian Żurek <sebzur>
-Benoit C <dzen>
-Chris Pickett <bunchesofdonald>
-Ben Timby <btimby>
-Michele Lazzeri <michelelazzeri-nextage>
-Camille Harang <mammique>
-Paul Oswald <poswald>
-Sean C. Farley <scfarley>
-Daniel Izquierdo <izquierdo>
-Can Yavuz <tschan>
-Shawn Lewis <shawnlewis>
-Adam Ness <greylurk>
-<yetist>
-Max Arnold <max-arnold>
-Ralph Broenink <ralphje>
-Simon Pantzare <pilt>
-Michael Barrett <phobologic>
-
-THANKS TO:
-
-Jesper Noehr <jespern> & 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.
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 025dccf1..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright (c) 2011, Tom Christie
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-Redistributions of source code must retain the above copyright notice, this
-list of conditions and the following disclaimer.
-Redistributions in binary form must reproduce the above copyright notice, this
-list of conditions and the following disclaimer in the documentation and/or
-other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-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.
diff --git a/MANIFEST.in b/MANIFEST.in
index 5c6a1c57..00e45086 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,2 @@
-recursive-include djangorestframework/static *.ico *.txt *.css
-recursive-include djangorestframework/templates *.txt *.html
-recursive-include examples .keep *.py *.txt
-recursive-include docs *.py *.rst *.html *.txt
-include AUTHORS LICENSE CHANGELOG.rst requirements.txt tox.ini
+recursive-include rest_framework/static *.js *.css *.png
+recursive-include rest_framework/templates *.txt *.html
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..a6f0438d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,101 @@
+# Django REST framework
+
+**A toolkit for building well-connected, self-describing web APIs.**
+
+**Author:** Tom Christie. [Follow me on Twitter][twitter]
+
+[![build-status-image]][travis]
+
+---
+
+**Full documentation for REST framework is available on [http://django-rest-framework.org][docs].**
+
+Note that this is the 2.0 version of REST framework. If you are looking for earlier versions please see the [0.4.x branch][0.4] on GitHub.
+
+---
+
+# Overview
+
+Django REST framework is a lightweight library that makes it easy to build Web APIs. It is designed as a modular and easy to customize architecture, based on Django's class based views.
+
+Web APIs built using REST framework are fully self-describing and web browseable - a huge useability win for your developers. It also supports a wide range of media types, authentication and permission policies out of the box.
+
+If you are considering using REST framework for your API, we recommend reading the [REST framework 2 announcment][rest-framework-2-announcement] which gives a good overview of the framework and it's capabilities.
+
+There is also a sandbox API you can use for testing purposes, [available here][sandbox].
+
+# Requirements
+
+* Python (2.6, 2.7)
+* Django (1.3, 1.4, 1.5)
+
+**Optional:**
+
+* [Markdown] - Markdown support for the self describing API.
+* [PyYAML] - YAML content type support.
+
+# Installation
+
+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
+
+# Development
+
+To build the docs.
+
+ ./mkdocs.py
+
+To run the tests.
+
+ ./rest_framework/runtests/runtests.py
+
+# Changelog
+
+## 2.0.0
+
+* Redesign of core components.
+* Fix **all of the things**.
+
+# License
+
+Copyright (c) 2011, Tom Christie
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+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.
+
+[build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=restframework2
+[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=restframework2
+[twitter]: https://twitter.com/_tomchristie
+[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
+[sandbox]: http://restframework.herokuapp.com/
+[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md
+
+[docs]: http://tomchristie.github.com/django-rest-framework/
+[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 2c8dabd4..00000000
--- a/README.rst
+++ /dev/null
@@ -1,90 +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 <https://twitter.com/_tomchristie>`_.
-
-:build status: |build-image|
-
-.. |build-image| image:: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=master
- :target: https://secure.travis-ci.org/tomchristie/django-rest-framework
-
-**Important!** REST framework 2.0 is due to be released to PyPI by the end of October.
-If you are considering building a new API using REST framework you should start working
-with the `restframework2` branch, and refering to the upcoming `REST framework 2 docs <http://tomchristie.github.com/django-rest-framework>`_.
-
-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 <https://github.com/tomchristie/django-rest-framework/issues>`_.
-General questions should be taken to the `discussion group <http://groups.google.com/group/django-rest-framework>`_.
-
-Requirements:
-
-* Python 2.6+
-* Django 1.3+
-
-
-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/djangorestframework/authentication.py b/djangorestframework/authentication.py
deleted file mode 100644
index f46a9c46..00000000
--- a/djangorestframework/authentication.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""
-The :mod:`authentication` module provides a set of pluggable authentication classes.
-
-Authentication behavior is provided by mixing the :class:`mixins.AuthMixin` class into a :class:`View` class.
-
-The set of authentication methods which are used is then specified by setting the
-:attr:`authentication` attribute on the :class:`View` class, and listing a set of :class:`authentication` classes.
-"""
-
-from django.contrib.auth import authenticate
-from djangorestframework.compat import CsrfViewMiddleware
-import base64
-
-__all__ = (
- 'BaseAuthentication',
- 'BasicAuthentication',
- 'UserLoggedInAuthentication'
-)
-
-
-class BaseAuthentication(object):
- """
- All authentication classes should extend BaseAuthentication.
- """
-
- def __init__(self, view):
- """
- :class:`Authentication` classes are always passed the current view on creation.
- """
- self.view = view
-
- def authenticate(self, request):
- """
- Authenticate the :obj:`request` and return a :obj:`User` or :const:`None`. [*]_
-
- .. [*] The authentication context *will* typically be a :obj:`User`,
- but it need not be. It can be any user-like object so long as the
- permissions classes (see the :mod:`permissions` module) on the view can
- handle the object and use it to determine if the request has the required
- permissions or not.
-
- This can be an important distinction if you're implementing some token
- based authentication mechanism, where the authentication context
- may be more involved than simply mapping to a :obj:`User`.
- """
- return None
-
-
-class BasicAuthentication(BaseAuthentication):
- """
- Use HTTP Basic authentication.
- """
-
- def authenticate(self, request):
- """
- Returns a :obj:`User` if a correct username and password have been supplied
- using HTTP Basic authentication. Otherwise returns :const:`None`.
- """
- from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
-
- if 'HTTP_AUTHORIZATION' in request.META:
- auth = request.META['HTTP_AUTHORIZATION'].split()
- if len(auth) == 2 and auth[0].lower() == "basic":
- try:
- auth_parts = base64.b64decode(auth[1]).partition(':')
- except TypeError:
- return None
-
- try:
- uname, passwd = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2])
- except DjangoUnicodeDecodeError:
- return None
-
- user = authenticate(username=uname, password=passwd)
- if user is not None and user.is_active:
- return user
- return None
-
-
-class UserLoggedInAuthentication(BaseAuthentication):
- """
- Use Django's session framework for authentication.
- """
-
- def authenticate(self, request):
- """
- Returns a :obj:`User` if the request session currently has a logged in user.
- Otherwise returns :const:`None`.
- """
- self.view.DATA # Make sure our generic parsing runs first
-
- if getattr(request, 'user', None) and request.user.is_active:
- # Enforce CSRF validation for session based authentication.
- resp = CsrfViewMiddleware().process_view(request, None, (), {})
-
- if resp is None: # csrf passed
- return request.user
- return None
-
-
-# TODO: TokenAuthentication, DigestAuthentication, OAuthAuthentication
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
deleted file mode 100644
index 4a453957..00000000
--- a/djangorestframework/mixins.py
+++ /dev/null
@@ -1,736 +0,0 @@
-"""
-The :mod:`mixins` module provides a set of reusable `mixin`
-classes that can be added to a `View`.
-"""
-
-from django.contrib.auth.models import AnonymousUser
-from django.core.paginator import Paginator
-from django.db.models.fields.related import ForeignKey
-from django.http import HttpResponse
-from urlobject import URLObject
-
-from djangorestframework import status
-from djangorestframework.renderers import BaseRenderer
-from djangorestframework.resources import Resource, FormResource, ModelResource
-from djangorestframework.response import Response, ErrorResponse
-from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX
-from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
-
-from StringIO import StringIO
-
-
-__all__ = (
- # Base behavior mixins
- 'RequestMixin',
- 'ResponseMixin',
- 'AuthMixin',
- 'ResourceMixin',
- # Model behavior mixins
- 'ReadModelMixin',
- 'CreateModelMixin',
- 'UpdateModelMixin',
- 'DeleteModelMixin',
- 'ListModelMixin',
- 'PaginatorMixin'
-)
-
-
-########## Request Mixin ##########
-
-class RequestMixin(object):
- """
- `Mixin` class to provide request parsing behavior.
- """
-
- _USE_FORM_OVERLOADING = True
- _METHOD_PARAM = '_method'
- _CONTENTTYPE_PARAM = '_content_type'
- _CONTENT_PARAM = '_content'
-
- parsers = ()
- """
- The set of request parsers that the view can handle.
-
- Should be a tuple/list of classes as described in the :mod:`parsers` module.
- """
-
- @property
- def method(self):
- """
- Returns the HTTP method.
-
- This should be used instead of just reading :const:`request.method`, as it allows the `method`
- to be overridden by using a hidden `form` field on a form POST request.
- """
- if not hasattr(self, '_method'):
- self._load_method_and_content_type()
- return self._method
-
- @property
- def content_type(self):
- """
- Returns the content type header.
-
- This should be used instead of ``request.META.get('HTTP_CONTENT_TYPE')``,
- as it allows the content type to be overridden by using a hidden form
- field on a form POST request.
- """
- if not hasattr(self, '_content_type'):
- self._load_method_and_content_type()
- return self._content_type
-
- @property
- def DATA(self):
- """
- Parses the request body and returns the data.
-
- Similar to ``request.POST``, except that it handles arbitrary parsers,
- and also works on methods other than POST (eg PUT).
- """
- if not hasattr(self, '_data'):
- self._load_data_and_files()
- return self._data
-
- @property
- def FILES(self):
- """
- Parses the request body and returns the files.
- Similar to ``request.FILES``, except that it handles arbitrary parsers,
- and also works on methods other than POST (eg PUT).
- """
- if not hasattr(self, '_files'):
- self._load_data_and_files()
- return self._files
-
- def _load_data_and_files(self):
- """
- Parse the request content into self.DATA and self.FILES.
- """
- if not hasattr(self, '_content_type'):
- self._load_method_and_content_type()
-
- if not hasattr(self, '_data'):
- (self._data, self._files) = self._parse(self._get_stream(), self._content_type)
-
- def _load_method_and_content_type(self):
- """
- Set the method and content_type, and then check if they've been overridden.
- """
- self._method = self.request.method
- self._content_type = self.request.META.get('HTTP_CONTENT_TYPE', self.request.META.get('CONTENT_TYPE', ''))
- self._perform_form_overloading()
-
- def _get_stream(self):
- """
- Returns an object that may be used to stream the request content.
- """
- request = self.request
-
- try:
- content_length = int(request.META.get('CONTENT_LENGTH', request.META.get('HTTP_CONTENT_LENGTH')))
- except (ValueError, TypeError):
- content_length = 0
-
- # TODO: Add 1.3's LimitedStream to compat and use that.
- # NOTE: Currently only supports parsing request body as a stream with 1.3
- if content_length == 0:
- return None
- elif hasattr(request, 'read'):
- return request
- return StringIO(request.raw_post_data)
-
- def _perform_form_overloading(self):
- """
- If this is a form POST request, then we need to check if the method and content/content_type have been
- overridden by setting them in hidden form fields or not.
- """
-
- # We only need to use form overloading on form POST requests.
- if not self._USE_FORM_OVERLOADING or self._method != 'POST' or not is_form_media_type(self._content_type):
- return
-
- # At this point we're committed to parsing the request as form data.
- self._data = data = self.request.POST.copy()
- self._files = self.request.FILES
-
- # Method overloading - change the method and remove the param from the content.
- if self._METHOD_PARAM in data:
- # NOTE: unlike `get`, `pop` on a `QueryDict` seems to return a list of values.
- self._method = self._data.pop(self._METHOD_PARAM)[0].upper()
-
- # Content overloading - modify the content type, and re-parse.
- if self._CONTENT_PARAM in data and self._CONTENTTYPE_PARAM in data:
- self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0]
- stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
- (self._data, self._files) = self._parse(stream, self._content_type)
-
- def _parse(self, stream, content_type):
- """
- Parse the request content.
-
- May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 ErrorResponse (Bad Request).
- """
- if stream is None or content_type is None:
- return (None, None)
-
- parsers = as_tuple(self.parsers)
-
- for parser_cls in parsers:
- parser = parser_cls(self)
- if parser.can_handle_request(content_type):
- return parser.parse(stream)
-
- raise ErrorResponse(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
- {'detail': 'Unsupported media type in request \'%s\'.' %
- content_type})
-
- @property
- def _parsed_media_types(self):
- """
- Return a list of all the media types that this view can parse.
- """
- return [parser.media_type for parser in self.parsers]
-
- @property
- def _default_parser(self):
- """
- Return the view's default parser class.
- """
- return self.parsers[0]
-
-
-########## ResponseMixin ##########
-
-class ResponseMixin(object):
- """
- Adds behavior for pluggable `Renderers` to a :class:`views.View` class.
-
- Default behavior is to use standard HTTP Accept header content negotiation.
- Also supports overriding the content type by specifying an ``_accept=`` parameter in the URL.
- Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.
- """
-
- _ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
- _IGNORE_IE_ACCEPT_HEADER = True
-
- renderers = ()
- """
- The set of response renderers that the view can handle.
-
- Should be a tuple/list of classes as described in the :mod:`renderers` module.
- """
-
- def get_renderers(self):
- """
- Return an iterable of available renderers. Override if you want to change
- this list at runtime, say depending on what settings you have enabled.
- """
- return self.renderers
-
- # TODO: wrap this behavior around dispatch(), ensuring it works
- # out of the box with existing Django classes that use render_to_response.
- def render(self, response):
- """
- Takes a :obj:`Response` object and returns an :obj:`HttpResponse`.
- """
- self.response = response
-
- try:
- renderer, media_type = self._determine_renderer(self.request)
- except ErrorResponse, exc:
- renderer = self._default_renderer(self)
- media_type = renderer.media_type
- response = exc.response
-
- # Set the media type of the response
- # Note that the renderer *could* override it in .render() if required.
- response.media_type = renderer.media_type
-
- # Serialize the response content
- if response.has_content_body:
- content = renderer.render(response.cleaned_content, media_type)
- else:
- content = renderer.render()
-
- # Build the HTTP Response
- resp = HttpResponse(content, mimetype=response.media_type, status=response.status)
- for (key, val) in response.headers.items():
- resp[key] = val
-
- return resp
-
- def _determine_renderer(self, request):
- """
- Determines the appropriate renderer for the output, given the client's 'Accept' header,
- and the :attr:`renderers` set on this class.
-
- Returns a 2-tuple of `(renderer, media_type)`
-
- See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
- """
-
- if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
- # Use _accept parameter override
- accept_list = [request.GET.get(self._ACCEPT_QUERY_PARAM)]
- elif (self._IGNORE_IE_ACCEPT_HEADER and
- 'HTTP_USER_AGENT' in request.META and
- MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT']) and
- request.META.get('HTTP_X_REQUESTED_WITH', '') != 'XMLHttpRequest'):
- # Ignore MSIE's broken accept behavior and do something sensible instead
- accept_list = ['text/html', '*/*']
- elif 'HTTP_ACCEPT' in request.META:
- # Use standard HTTP Accept negotiation
- accept_list = [token.strip() for token in request.META['HTTP_ACCEPT'].split(',')]
- else:
- # No accept header specified
- accept_list = ['*/*']
-
- # Check the acceptable media types against each renderer,
- # attempting more specific media types first
- # NB. The inner loop here isn't as bad as it first looks :)
- # Worst case is we're looping over len(accept_list) * len(self.renderers)
- renderers = [renderer_cls(self) for renderer_cls in self.get_renderers()]
-
- for accepted_media_type_lst in order_by_precedence(accept_list):
- for renderer in renderers:
- for accepted_media_type in accepted_media_type_lst:
- if renderer.can_handle_response(accepted_media_type):
- return renderer, accepted_media_type
-
- # No acceptable renderers were found
- raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
- {'detail': 'Could not satisfy the client\'s Accept header',
- 'available_types': self._rendered_media_types})
-
- @property
- def _rendered_media_types(self):
- """
- Return an list of all the media types that this view can render.
- """
- return [renderer.media_type for renderer in self.renderers]
-
- @property
- def _rendered_formats(self):
- """
- Return a list of all the formats that this view can render.
- """
- return [renderer.format for renderer in self.renderers]
-
- @property
- def _default_renderer(self):
- """
- Return the view's default renderer class.
- """
- return self.renderers[0]
-
-
-########## Auth Mixin ##########
-
-class AuthMixin(object):
- """
- Simple :class:`mixin` class to add authentication and permission checking to a :class:`View` class.
- """
-
- authentication = ()
- """
- The set of authentication types that this view can handle.
-
- Should be a tuple/list of classes as described in the :mod:`authentication` module.
- """
-
- permissions = ()
- """
- The set of permissions that will be enforced on this view.
-
- Should be a tuple/list of classes as described in the :mod:`permissions` module.
- """
-
- @property
- def user(self):
- """
- Returns the :obj:`user` for the current request, as determined by the set of
- :class:`authentication` classes applied to the :class:`View`.
- """
- if not hasattr(self, '_user'):
- self._user = self._authenticate()
- return self._user
-
- def _authenticate(self):
- """
- Attempt to authenticate the request using each authentication class in turn.
- Returns a ``User`` object, which may be ``AnonymousUser``.
- """
- for authentication_cls in self.authentication:
- authentication = authentication_cls(self)
- user = authentication.authenticate(self.request)
- if user:
- return user
- return AnonymousUser()
-
- # TODO: wrap this behavior around dispatch()
- def _check_permissions(self):
- """
- Check user permissions and either raise an ``ErrorResponse`` or return.
- """
- user = self.user
- for permission_cls in self.permissions:
- permission = permission_cls(self)
- permission.check_permission(user)
-
-
-########## Resource Mixin ##########
-
-class ResourceMixin(object):
- """
- Provides request validation and response filtering behavior.
-
- Should be a class as described in the :mod:`resources` module.
-
- The :obj:`resource` is an object that maps a view onto it's representation on the server.
-
- It provides validation on the content of incoming requests,
- and filters the object representation into a serializable object for the response.
- """
- resource = None
-
- @property
- def CONTENT(self):
- """
- Returns the cleaned, validated request content.
-
- May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
- """
- if not hasattr(self, '_content'):
- self._content = self.validate_request(self.DATA, self.FILES)
- return self._content
-
- @property
- def PARAMS(self):
- """
- Returns the cleaned, validated query parameters.
-
- May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
- """
- return self.validate_request(self.request.GET)
-
- @property
- def _resource(self):
- if self.resource:
- return self.resource(self)
- elif getattr(self, 'model', None):
- return ModelResource(self)
- elif getattr(self, 'form', None):
- return FormResource(self)
- elif getattr(self, '%s_form' % self.method.lower(), None):
- return FormResource(self)
- return Resource(self)
-
- def validate_request(self, data, files=None):
- """
- Given the request *data* and optional *files*, return the cleaned, validated content.
- May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
- """
- return self._resource.validate_request(data, files)
-
- def filter_response(self, obj):
- """
- Given the response content, filter it into a serializable object.
- """
- return self._resource.filter_response(obj)
-
- def get_bound_form(self, content=None, method=None):
- if hasattr(self._resource, 'get_bound_form'):
- return self._resource.get_bound_form(content, method=method)
- else:
- return None
-
-
-########## Model Mixins ##########
-
-class ModelMixin(object):
- """ Implements mechanisms used by other classes (like *ModelMixin group) to
- define a query that represents Model instances the Mixin is working with.
-
- If a *ModelMixin is going to retrive an instance (or queryset) using args and kwargs
- passed by as URL arguments, it should provied arguments to objects.get and objects.filter
- methods wrapped in by `build_query`
-
- If a *ModelMixin is going to create/update an instance get_instance_data
- handles the instance data creation/preaparation.
- """
-
- queryset = None
-
- def get_query_kwargs(self, *args, **kwargs):
- """
- Return a dict of kwargs that will be used to build the
- model instance retrieval or to filter querysets.
- """
-
- kwargs = dict(kwargs)
-
- # If the URLconf includes a .(?P<format>\w+) pattern to match against
- # a .json, .xml suffix, then drop the 'format' kwarg before
- # constructing the query.
- if BaseRenderer._FORMAT_QUERY_PARAM in kwargs:
- del kwargs[BaseRenderer._FORMAT_QUERY_PARAM]
-
- return kwargs
-
- def get_instance_data(self, model, content, **kwargs):
- """
- Returns the dict with the data for model instance creation/update.
-
- Arguments:
- - model: model class (django.db.models.Model subclass) to work with
- - content: a dictionary with instance data
- - kwargs: a dict of URL provided keyword arguments
-
- The create/update queries are created basicly with the contet provided
- with POST/PUT HTML methods and kwargs passed in the URL. This methods
- simply merges the URL data and the content preaparing the ready-to-use
- data dictionary.
- """
-
- tmp = dict(kwargs)
-
- for field in model._meta.fields:
- if isinstance(field, ForeignKey) and field.name in tmp:
- # translate 'related_field' kwargs into 'related_field_id'
- tmp[field.name + '_id'] = tmp[field.name]
- del tmp[field.name]
-
- all_kw_args = dict(content.items() + tmp.items())
-
- return all_kw_args
-
- def get_instance(self, **kwargs):
- """
- Get a model instance for read/update/delete requests.
- """
- return self.get_queryset().get(**kwargs)
-
- def get_queryset(self):
- """
- Return the queryset for this view.
- """
- return getattr(self.resource, 'queryset',
- self.resource.model.objects.all())
-
- def get_ordering(self):
- """
- Return the ordering for this view.
- """
- return getattr(self.resource, 'ordering', None)
-
-
-class ReadModelMixin(ModelMixin):
- """
- Behavior to read a `model` instance on GET requests
- """
- def get(self, request, *args, **kwargs):
- model = self.resource.model
- query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
-
- try:
- self.model_instance = self.get_instance(**query_kwargs)
- except model.DoesNotExist:
- raise ErrorResponse(status.HTTP_404_NOT_FOUND)
-
- return self.model_instance
-
-
-class CreateModelMixin(ModelMixin):
- """
- Behavior to create a `model` instance on POST requests
- """
- def post(self, request, *args, **kwargs):
- model = self.resource.model
-
- # Copy the dict to keep self.CONTENT intact
- content = dict(self.CONTENT)
- m2m_data = {}
-
- for field in model._meta.many_to_many:
- if field.name in content:
- m2m_data[field.name] = (
- field.m2m_reverse_field_name(), content[field.name]
- )
- del content[field.name]
-
- instance = model(**self.get_instance_data(model, content, *args, **kwargs))
- instance.save()
-
- for fieldname in m2m_data:
- manager = getattr(instance, fieldname)
-
- if hasattr(manager, 'add'):
- manager.add(*m2m_data[fieldname][1])
- else:
- data = {}
- data[manager.source_field_name] = instance
-
- for related_item in m2m_data[fieldname][1]:
- data[m2m_data[fieldname][0]] = related_item
- manager.through(**data).save()
-
- headers = {}
- if hasattr(self.resource, 'url'):
- headers['Location'] = self.resource(self).url(instance)
- return Response(status.HTTP_201_CREATED, instance, headers)
-
-
-class UpdateModelMixin(ModelMixin):
- """
- Behavior to update a `model` instance on PUT requests
- """
- def put(self, request, *args, **kwargs):
- model = self.resource.model
- query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
-
- # TODO: update on the url of a non-existing resource url doesn't work
- # correctly at the moment - will end up with a new url
- try:
- self.model_instance = self.get_instance(**query_kwargs)
-
- for (key, val) in self.CONTENT.items():
- setattr(self.model_instance, key, val)
- except model.DoesNotExist:
- self.model_instance = model(**self.get_instance_data(model, self.CONTENT, *args, **kwargs))
- self.model_instance.save()
- return self.model_instance
-
-
-class DeleteModelMixin(ModelMixin):
- """
- Behavior to delete a `model` instance on DELETE requests
- """
- def delete(self, request, *args, **kwargs):
- model = self.resource.model
- query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
-
- try:
- instance = self.get_instance(**query_kwargs)
- except model.DoesNotExist:
- raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
-
- instance.delete()
- return
-
-
-class ListModelMixin(ModelMixin):
- """
- Behavior to list a set of `model` instances on GET requests
- """
-
- def get(self, request, *args, **kwargs):
- queryset = self.get_queryset()
- ordering = self.get_ordering()
- query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
-
- queryset = queryset.filter(**query_kwargs)
- if ordering:
- queryset = queryset.order_by(*ordering)
-
- return queryset
-
-
-########## Pagination Mixins ##########
-
-class PaginatorMixin(object):
- """
- Adds pagination support to GET requests
- Obviously should only be used on lists :)
-
- A default limit can be set by setting `limit` on the object. This will also
- be used as the maximum if the client sets the `limit` GET param
- """
- limit = 20
-
- def get_limit(self):
- """
- Helper method to determine what the `limit` should be
- """
- try:
- limit = int(self.request.GET.get('limit', self.limit))
- return min(limit, self.limit)
- except ValueError:
- return self.limit
-
- def url_with_page_number(self, page_number):
- """
- Constructs a url used for getting the next/previous urls
- """
- url = URLObject(self.request.get_full_path())
- url = url.set_query_param('page', str(page_number))
-
- limit = self.get_limit()
- if limit != self.limit:
- url = url.set_query_param('limit', str(limit))
-
- return url
-
- def next(self, page):
- """
- Returns a url to the next page of results (if any)
- """
- if not page.has_next():
- return None
-
- return self.url_with_page_number(page.next_page_number())
-
- def previous(self, page):
- """ Returns a url to the previous page of results (if any) """
- if not page.has_previous():
- return None
-
- return self.url_with_page_number(page.previous_page_number())
-
- def serialize_page_info(self, page):
- """
- This is some useful information that is added to the response
- """
- return {
- 'next': self.next(page),
- 'page': page.number,
- 'pages': page.paginator.num_pages,
- 'per_page': self.get_limit(),
- 'previous': self.previous(page),
- 'total': page.paginator.count,
- }
-
- def filter_response(self, obj):
- """
- Given the response content, paginate and then serialize.
-
- The response is modified to include to useful data relating to the number
- of objects, number of pages, next/previous urls etc. etc.
-
- The serialised objects are put into `results` on this new, modified
- response
- """
-
- # We don't want to paginate responses for anything other than GET requests
- if self.method.upper() != 'GET':
- return self._resource.filter_response(obj)
-
- paginator = Paginator(obj, self.get_limit())
-
- try:
- page_num = int(self.request.GET.get('page', '1'))
- except ValueError:
- raise ErrorResponse(status.HTTP_404_NOT_FOUND,
- {'detail': 'That page contains no results'})
-
- if page_num not in paginator.page_range:
- raise ErrorResponse(status.HTTP_404_NOT_FOUND,
- {'detail': 'That page contains no results'})
-
- page = paginator.page(page_num)
-
- serialized_object_list = self._resource.filter_response(page.object_list)
- serialized_page_info = self.serialize_page_info(page)
-
- serialized_page_info['results'] = serialized_object_list
-
- return serialized_page_info
diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py
deleted file mode 100644
index a19582b7..00000000
--- a/djangorestframework/parsers.py
+++ /dev/null
@@ -1,258 +0,0 @@
-"""
-Django supports parsing the content of an HTTP request, but only for form POST requests.
-That behavior is sufficient for dealing with standard HTML forms, but it doesn't map well
-to general HTTP requests.
-
-We need a method to be able to:
-
-1.) Determine the parsed content on a request for methods other than POST (eg typically also PUT)
-
-2.) Determine the parsed content on a request for media types other than application/x-www-form-urlencoded
- and multipart/form-data. (eg also handle multipart/json)
-"""
-
-from django.http import QueryDict
-from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
-from django.http.multipartparser import MultiPartParserError
-from django.utils import simplejson as json
-from djangorestframework import status
-from djangorestframework.compat import yaml
-from djangorestframework.response import ErrorResponse
-from djangorestframework.utils.mediatypes import media_type_matches
-from xml.etree import ElementTree as ET
-from djangorestframework.compat import ETParseError
-from xml.parsers.expat import ExpatError
-import datetime
-import decimal
-
-
-__all__ = (
- 'BaseParser',
- 'JSONParser',
- 'PlainTextParser',
- 'FormParser',
- 'MultiPartParser',
- 'YAMLParser',
- 'XMLParser'
-)
-
-
-class BaseParser(object):
- """
- All parsers should extend :class:`BaseParser`, specifying a :attr:`media_type` attribute,
- and overriding the :meth:`parse` method.
- """
-
- media_type = None
-
- def __init__(self, view):
- """
- Initialize the parser with the ``View`` instance as state,
- in case the parser needs to access any metadata on the :obj:`View` object.
- """
- self.view = view
-
- def can_handle_request(self, content_type):
- """
- Returns :const:`True` if this parser is able to deal with the given *content_type*.
-
- The default implementation for this function is to check the *content_type*
- argument against the :attr:`media_type` attribute set on the class to see if
- they match.
-
- This may be overridden to provide for other behavior, but typically you'll
- instead want to just set the :attr:`media_type` attribute on the class.
- """
- return media_type_matches(self.media_type, content_type)
-
- def parse(self, stream):
- """
- Given a *stream* to read from, return the deserialized output.
- Should return a 2-tuple of (data, files).
- """
- raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.")
-
-
-class JSONParser(BaseParser):
- """
- Parses JSON-serialized data.
- """
-
- media_type = 'application/json'
-
- def parse(self, stream):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be an object which is the parsed content of the response.
- `files` will always be `None`.
- """
- try:
- return (json.load(stream), None)
- except ValueError, exc:
- raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
- {'detail': 'JSON parse error - %s' % unicode(exc)})
-
-
-class YAMLParser(BaseParser):
- """
- Parses YAML-serialized data.
- """
-
- media_type = 'application/yaml'
-
- def parse(self, stream):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be an object which is the parsed content of the response.
- `files` will always be `None`.
- """
- try:
- return (yaml.safe_load(stream), None)
- except (ValueError, yaml.parser.ParserError), exc:
- content = {'detail': 'YAML parse error - %s' % unicode(exc)}
- raise ErrorResponse(status.HTTP_400_BAD_REQUEST, content)
-
-
-class PlainTextParser(BaseParser):
- """
- Plain text parser.
- """
-
- media_type = 'text/plain'
-
- def parse(self, stream):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will simply be a string representing the body of the request.
- `files` will always be `None`.
- """
- return (stream.read(), None)
-
-
-class FormParser(BaseParser):
- """
- Parser for form data.
- """
-
- media_type = 'application/x-www-form-urlencoded'
-
- def parse(self, stream):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be a :class:`QueryDict` containing all the form parameters.
- `files` will always be :const:`None`.
- """
- data = QueryDict(stream.read())
- return (data, None)
-
-
-class MultiPartParser(BaseParser):
- """
- Parser for multipart form data, which may include file data.
- """
-
- media_type = 'multipart/form-data'
-
- def parse(self, stream):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be a :class:`QueryDict` containing all the form parameters.
- `files` will be a :class:`QueryDict` containing all the form files.
- """
- upload_handlers = self.view.request._get_upload_handlers()
- try:
- django_parser = DjangoMultiPartParser(self.view.request.META, stream, upload_handlers)
- return django_parser.parse()
- except MultiPartParserError, exc:
- raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
- {'detail': 'multipart parse error - %s' % unicode(exc)})
-
-
-class XMLParser(BaseParser):
- """
- XML parser.
- """
-
- media_type = 'application/xml'
-
- def parse(self, stream):
- """
- Returns a 2-tuple of `(data, files)`.
-
- `data` will simply be a string representing the body of the request.
- `files` will always be `None`.
- """
- try:
- tree = ET.parse(stream)
- except (ExpatError, ETParseError, ValueError), exc:
- content = {'detail': 'XML parse error - %s' % unicode(exc)}
- raise ErrorResponse(status.HTTP_400_BAD_REQUEST, content)
- data = self._xml_convert(tree.getroot())
-
- return (data, None)
-
- def _xml_convert(self, element):
- """
- convert the xml `element` into the corresponding python object
- """
-
- children = element.getchildren()
-
- if len(children) == 0:
- return self._type_convert(element.text)
- else:
- # if the fist child tag is list-item means all children are list-item
- if children[0].tag == "list-item":
- data = []
- for child in children:
- data.append(self._xml_convert(child))
- else:
- data = {}
- for child in children:
- data[child.tag] = self._xml_convert(child)
-
- return data
-
- def _type_convert(self, value):
- """
- Converts the value returned by the XMl parse into the equivalent
- Python type
- """
- if value is None:
- return value
-
- try:
- return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
- except ValueError:
- pass
-
- try:
- return int(value)
- except ValueError:
- pass
-
- try:
- return decimal.Decimal(value)
- except decimal.InvalidOperation:
- pass
-
- return value
-
-
-DEFAULT_PARSERS = (
- JSONParser,
- FormParser,
- MultiPartParser,
- XMLParser
-)
-
-if yaml:
- DEFAULT_PARSERS += (YAMLParser, )
-else:
- YAMLParser = None
-
diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py
deleted file mode 100644
index 03d78c2e..00000000
--- a/djangorestframework/permissions.py
+++ /dev/null
@@ -1,267 +0,0 @@
-"""
-The :mod:`permissions` module bundles a set of permission classes that are used
-for checking if a request passes a certain set of constraints. You can assign a permission
-class to your view by setting your View's :attr:`permissions` class attribute.
-"""
-
-from django.core.cache import cache
-from djangorestframework import status
-from djangorestframework.response import ErrorResponse
-import time
-
-__all__ = (
- 'BasePermission',
- 'FullAnonAccess',
- 'IsAuthenticated',
- 'IsAdminUser',
- 'IsUserOrIsAnonReadOnly',
- 'PerUserThrottling',
- 'PerViewThrottling',
- 'PerResourceThrottling'
-)
-
-SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
-
-
-_403_FORBIDDEN_RESPONSE = ErrorResponse(
- status.HTTP_403_FORBIDDEN,
- {'detail': 'You do not have permission to access this resource. ' +
- 'You may need to login or otherwise authenticate the request.'})
-
-_503_SERVICE_UNAVAILABLE = ErrorResponse(
- status.HTTP_503_SERVICE_UNAVAILABLE,
- {'detail': 'request was throttled'})
-
-
-class BasePermission(object):
- """
- A base class from which all permission classes should inherit.
- """
- def __init__(self, view):
- """
- Permission classes are always passed the current view on creation.
- """
- self.view = view
-
- def check_permission(self, auth):
- """
- Should simply return, or raise an :exc:`response.ErrorResponse`.
- """
- pass
-
-
-class FullAnonAccess(BasePermission):
- """
- Allows full access.
- """
-
- def check_permission(self, user):
- pass
-
-
-class IsAuthenticated(BasePermission):
- """
- Allows access only to authenticated users.
- """
-
- def check_permission(self, user):
- if not user.is_authenticated():
- raise _403_FORBIDDEN_RESPONSE
-
-
-class IsAdminUser(BasePermission):
- """
- Allows access only to admin users.
- """
-
- def check_permission(self, user):
- if not user.is_staff:
- raise _403_FORBIDDEN_RESPONSE
-
-
-class IsUserOrIsAnonReadOnly(BasePermission):
- """
- The request is authenticated as a user, or is a read-only request.
- """
-
- def check_permission(self, user):
- if (not user.is_authenticated() and
- self.view.method not in SAFE_METHODS):
- raise _403_FORBIDDEN_RESPONSE
-
-
-class DjangoModelPermissions(BasePermission):
- """
- The request is authenticated using `django.contrib.auth` permissions.
- See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
-
- It ensures that the user is authenticated, and has the appropriate
- `add`/`change`/`delete` permissions on the model.
-
- This permission should only be used on views with a `ModelResource`.
- """
-
- # Map methods into required permission codes.
- # Override this if you need to also provide 'read' permissions,
- # or if you want to provide custom permission codes.
- perms_map = {
- 'GET': [],
- 'OPTIONS': [],
- 'HEAD': [],
- 'POST': ['%(app_label)s.add_%(model_name)s'],
- 'PUT': ['%(app_label)s.change_%(model_name)s'],
- 'PATCH': ['%(app_label)s.change_%(model_name)s'],
- 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
- }
-
- def get_required_permissions(self, method, model_cls):
- """
- Given a model and an HTTP method, return the list of permission
- codes that the user is required to have.
- """
- kwargs = {
- 'app_label': model_cls._meta.app_label,
- 'model_name': model_cls._meta.module_name
- }
- try:
- return [perm % kwargs for perm in self.perms_map[method]]
- except KeyError:
- ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED)
-
- def check_permission(self, user):
- method = self.view.method
- model_cls = self.view.resource.model
- perms = self.get_required_permissions(method, model_cls)
-
- if not user.is_authenticated or not user.has_perms(perms):
- raise _403_FORBIDDEN_RESPONSE
-
-
-class BaseThrottle(BasePermission):
- """
- Rate throttling of requests.
-
- The rate (requests / seconds) is set by a :attr:`throttle` attribute
- on the :class:`.View` class. The attribute is a string of the form 'number of
- requests/period'.
-
- Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
-
- Previous request information used for throttling is stored in the cache.
- """
-
- attr_name = 'throttle'
- default = '0/sec'
- timer = time.time
-
- def get_cache_key(self):
- """
- Should return a unique cache-key which can be used for throttling.
- Must be overridden.
- """
- pass
-
- def check_permission(self, auth):
- """
- Check the throttling.
- Return `None` or raise an :exc:`.ErrorResponse`.
- """
- num, period = getattr(self.view, self.attr_name, self.default).split('/')
- self.num_requests = int(num)
- self.duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
- self.auth = auth
- self.check_throttle()
-
- def check_throttle(self):
- """
- Implement the check to see if the request should be throttled.
-
- On success calls :meth:`throttle_success`.
- On failure calls :meth:`throttle_failure`.
- """
- self.key = self.get_cache_key()
- self.history = cache.get(self.key, [])
- self.now = self.timer()
-
- # Drop any requests from the history which have now passed the
- # throttle duration
- while self.history and self.history[-1] <= self.now - self.duration:
- self.history.pop()
- if len(self.history) >= self.num_requests:
- self.throttle_failure()
- else:
- self.throttle_success()
-
- def throttle_success(self):
- """
- Inserts the current request's timestamp along with the key
- into the cache.
- """
- self.history.insert(0, self.now)
- cache.set(self.key, self.history, self.duration)
- header = 'status=SUCCESS; next=%s sec' % self.next()
- self.view.add_header('X-Throttle', header)
-
- def throttle_failure(self):
- """
- Called when a request to the API has failed due to throttling.
- Raises a '503 service unavailable' response.
- """
- header = 'status=FAILURE; next=%s sec' % self.next()
- self.view.add_header('X-Throttle', header)
- raise _503_SERVICE_UNAVAILABLE
-
- def next(self):
- """
- Returns the recommended next request time in seconds.
- """
- if self.history:
- remaining_duration = self.duration - (self.now - self.history[-1])
- else:
- remaining_duration = self.duration
-
- available_requests = self.num_requests - len(self.history) + 1
-
- return '%.2f' % (remaining_duration / float(available_requests))
-
-
-class PerUserThrottling(BaseThrottle):
- """
- Limits the rate of API calls that may be made by a given user.
-
- The user id will be used as a unique identifier if the user is
- authenticated. For anonymous requests, the IP address of the client will
- be used.
- """
-
- def get_cache_key(self):
- if self.auth.is_authenticated():
- ident = self.auth.id
- else:
- ident = self.view.request.META.get('REMOTE_ADDR', None)
- return 'throttle_user_%s' % ident
-
-
-class PerViewThrottling(BaseThrottle):
- """
- Limits the rate of API calls that may be used on a given view.
-
- The class name of the view is used as a unique identifier to
- throttle against.
- """
-
- def get_cache_key(self):
- return 'throttle_view_%s' % self.view.__class__.__name__
-
-
-class PerResourceThrottling(BaseThrottle):
- """
- Limits the rate of API calls that may be used against all views on
- a given resource.
-
- The class name of the resource is used as a unique identifier to
- throttle against.
- """
-
- def get_cache_key(self):
- return 'throttle_resource_%s' % self.view.resource.__class__.__name__
diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py
deleted file mode 100644
index 772002ba..00000000
--- a/djangorestframework/renderers.py
+++ /dev/null
@@ -1,417 +0,0 @@
-"""
-Renderers are used to serialize a View's output into specific media types.
-
-Django REST framework also provides HTML and PlainText renderers that help self-document the API,
-by serializing the output along with documentation regarding the View, output status and headers,
-and providing forms and links depending on the allowed methods, renderers and parsers on the View.
-"""
-from django import forms
-from django.conf import settings
-from django.core.serializers.json import DateTimeAwareJSONEncoder
-from django.template import RequestContext, loader
-from django.utils import simplejson as json
-
-
-from djangorestframework.compat import yaml
-from djangorestframework.utils import dict2xml, url_resolves
-from djangorestframework.utils.breadcrumbs import get_breadcrumbs
-from djangorestframework.utils.mediatypes import get_media_type_params, add_media_type_param, media_type_matches
-from djangorestframework import VERSION
-
-import string
-from urllib import quote_plus
-
-__all__ = (
- 'BaseRenderer',
- 'TemplateRenderer',
- 'JSONRenderer',
- 'JSONPRenderer',
- 'DocumentingHTMLRenderer',
- 'DocumentingXHTMLRenderer',
- 'DocumentingPlainTextRenderer',
- 'XMLRenderer',
- 'YAMLRenderer'
-)
-
-
-class BaseRenderer(object):
- """
- All renderers must extend this class, set the :attr:`media_type` attribute,
- and override the :meth:`render` method.
- """
-
- _FORMAT_QUERY_PARAM = 'format'
-
- media_type = None
- format = None
-
- def __init__(self, view):
- self.view = view
-
- def can_handle_response(self, accept):
- """
- Returns :const:`True` if this renderer is able to deal with the given
- *accept* media type.
-
- The default implementation for this function is to check the *accept*
- argument against the :attr:`media_type` attribute set on the class to see if
- they match.
-
- This may be overridden to provide for other behavior, but typically you'll
- instead want to just set the :attr:`media_type` attribute on the class.
- """
- format = self.view.kwargs.get(self._FORMAT_QUERY_PARAM, None)
- if format is None:
- format = self.view.request.GET.get(self._FORMAT_QUERY_PARAM, None)
- if format is not None:
- return format == self.format
- return media_type_matches(self.media_type, accept)
-
- def render(self, obj=None, media_type=None):
- """
- Given an object render it into a string.
-
- The requested media type is also passed to this method,
- as it may contain parameters relevant to how the parser
- should render the output.
- EG: ``application/json; indent=4``
-
- By default render simply returns the output as-is.
- Override this method to provide for other behavior.
- """
- if obj is None:
- return ''
-
- return str(obj)
-
-
-class JSONRenderer(BaseRenderer):
- """
- Renderer which serializes to JSON
- """
-
- media_type = 'application/json'
- format = 'json'
-
- def render(self, obj=None, media_type=None):
- """
- Renders *obj* into serialized JSON.
- """
- if obj is None:
- return ''
-
- # If the media type looks like 'application/json; indent=4', then
- # pretty print the result.
- indent = get_media_type_params(media_type).get('indent', None)
- sort_keys = False
- try:
- indent = max(min(int(indent), 8), 0)
- sort_keys = True
- except (ValueError, TypeError):
- indent = None
-
- return json.dumps(obj, cls=DateTimeAwareJSONEncoder, indent=indent, sort_keys=sort_keys)
-
-
-class JSONPRenderer(JSONRenderer):
- """
- Renderer which serializes to JSONP
- """
-
- media_type = 'application/json-p'
- format = 'json-p'
- renderer_class = JSONRenderer
- callback_parameter = 'callback'
-
- def _get_callback(self):
- return self.view.request.GET.get(self.callback_parameter, self.callback_parameter)
-
- def _get_renderer(self):
- return self.renderer_class(self.view)
-
- def render(self, obj=None, media_type=None):
- callback = self._get_callback()
- json = self._get_renderer().render(obj, media_type)
- return "%s(%s);" % (callback, json)
-
-
-class XMLRenderer(BaseRenderer):
- """
- Renderer which serializes to XML.
- """
-
- media_type = 'application/xml'
- format = 'xml'
-
- def render(self, obj=None, media_type=None):
- """
- Renders *obj* into serialized XML.
- """
- if obj is None:
- return ''
- return dict2xml(obj)
-
-
-class YAMLRenderer(BaseRenderer):
- """
- Renderer which serializes to YAML.
- """
-
- media_type = 'application/yaml'
- format = 'yaml'
-
- def render(self, obj=None, media_type=None):
- """
- Renders *obj* into serialized YAML.
- """
- if obj is None:
- return ''
-
- return yaml.safe_dump(obj)
-
-
-class TemplateRenderer(BaseRenderer):
- """
- A Base class provided for convenience.
-
- Render the object simply by using the given template.
- To create a template renderer, subclass this class, and set
- the :attr:`media_type` and :attr:`template` attributes.
- """
-
- media_type = None
- template = None
-
- def __init__(self, view):
- super(TemplateRenderer, self).__init__(view)
- self.template = getattr(self.view, "template", self.template)
-
- def render(self, obj=None, media_type=None):
- """
- Renders *obj* using the :attr:`template` specified on the class.
- """
- if obj is None:
- return ''
-
- template = loader.get_template(self.template)
- context = RequestContext(self.view.request, {'object': obj})
- return template.render(context)
-
-
-class DocumentingTemplateRenderer(BaseRenderer):
- """
- Base class for renderers used to self-document the API.
- Implementing classes should extend this class and set the template attribute.
- """
-
- template = None
-
- def __init__(self, view):
- super(DocumentingTemplateRenderer, self).__init__(view)
- self.template = getattr(self.view, "template", self.template)
-
- def _get_content(self, view, request, obj, media_type):
- """
- Get the content as if it had been rendered by a non-documenting renderer.
-
- (Typically this will be the content as it would have been if the Resource had been
- requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)
- """
-
- # Find the first valid renderer and render the content. (Don't use another documenting renderer.)
- renderers = [renderer for renderer in view.renderers if not issubclass(renderer, DocumentingTemplateRenderer)]
- if not renderers:
- return '[No renderers were found]'
-
- media_type = add_media_type_param(media_type, 'indent', '4')
- content = renderers[0](view).render(obj, media_type)
- if not all(char in string.printable for char in content):
- return '[%d bytes of binary content]'
-
- return content
-
- def _get_form_instance(self, view, method):
- """
- Get a form, possibly bound to either the input or output data.
- In the absence on of the Resource having an associated form then
- provide a form that can be used to submit arbitrary content.
- """
-
- # Get the form instance if we have one bound to the input
- form_instance = None
- if method == getattr(view, 'method', view.request.method).lower():
- form_instance = getattr(view, 'bound_form_instance', None)
-
- if not form_instance and hasattr(view, 'get_bound_form'):
- # Otherwise if we have a response that is valid against the form then use that
- if view.response.has_content_body:
- try:
- form_instance = view.get_bound_form(view.response.cleaned_content, method=method)
- if form_instance and not form_instance.is_valid():
- form_instance = None
- except Exception:
- form_instance = None
-
- # If we still don't have a form instance then try to get an unbound form
- if not form_instance:
- try:
- form_instance = view.get_bound_form(method=method)
- except Exception:
- pass
-
- # If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
- if not form_instance:
- form_instance = self._get_generic_content_form(view)
-
- return form_instance
-
- def _get_generic_content_form(self, view):
- """
- Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
- (Which are typically application/x-www-form-urlencoded)
- """
-
- # If we're not using content overloading there's no point in supplying a generic form,
- # as the view won't treat the form's value as the content of the request.
- if not getattr(view, '_USE_FORM_OVERLOADING', False):
- return None
-
- # NB. http://jacobian.org/writing/dynamic-form-generation/
- class GenericContentForm(forms.Form):
- def __init__(self, view):
- """We don't know the names of the fields we want to set until the point the form is instantiated,
- as they are determined by the Resource the form is being created against.
- Add the fields dynamically."""
- super(GenericContentForm, self).__init__()
-
- contenttype_choices = [(media_type, media_type) for media_type in view._parsed_media_types]
- initial_contenttype = view._default_parser.media_type
-
- self.fields[view._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type',
- choices=contenttype_choices,
- initial=initial_contenttype)
- self.fields[view._CONTENT_PARAM] = forms.CharField(label='Content',
- widget=forms.Textarea)
-
- # If either of these reserved parameters are turned off then content tunneling is not possible
- if self.view._CONTENTTYPE_PARAM is None or self.view._CONTENT_PARAM is None:
- return None
-
- # Okey doke, let's do it
- return GenericContentForm(view)
-
- def get_name(self):
- try:
- return self.view.get_name()
- except AttributeError:
- return self.view.__doc__
-
- def get_description(self, html=None):
- if html is None:
- html = bool('html' in self.format)
- try:
- return self.view.get_description(html)
- except AttributeError:
- return self.view.__doc__
-
- def render(self, obj=None, media_type=None):
- """
- Renders *obj* using the :attr:`template` set on the class.
-
- The context used in the template contains all the information
- needed to self-document the response to this request.
- """
-
- content = self._get_content(self.view, self.view.request, obj, media_type)
-
- put_form_instance = self._get_form_instance(self.view, 'put')
- post_form_instance = self._get_form_instance(self.view, 'post')
-
- if url_resolves(settings.LOGIN_URL) and url_resolves(settings.LOGOUT_URL):
- login_url = "%s?next=%s" % (settings.LOGIN_URL, quote_plus(self.view.request.path))
- logout_url = "%s?next=%s" % (settings.LOGOUT_URL, quote_plus(self.view.request.path))
- else:
- login_url = None
- logout_url = None
-
- name = self.get_name()
- description = self.get_description()
-
- breadcrumb_list = get_breadcrumbs(self.view.request.path)
-
- template = loader.get_template(self.template)
- context = RequestContext(self.view.request, {
- 'content': content,
- 'view': self.view,
- 'request': self.view.request,
- 'response': self.view.response,
- 'description': description,
- 'name': name,
- 'version': VERSION,
- 'breadcrumblist': breadcrumb_list,
- 'available_formats': self.view._rendered_formats,
- 'put_form': put_form_instance,
- 'post_form': post_form_instance,
- 'FORMAT_PARAM': self._FORMAT_QUERY_PARAM,
- 'METHOD_PARAM': getattr(self.view, '_METHOD_PARAM', None),
- })
-
- ret = template.render(context)
-
- # Munge DELETE Response code to allow us to return content
- # (Do this *after* we've rendered the template so that we include
- # the normal deletion response code in the output)
- if self.view.response.status == 204:
- self.view.response.status = 200
-
- return ret
-
-
-class DocumentingHTMLRenderer(DocumentingTemplateRenderer):
- """
- Renderer which provides a browsable HTML interface for an API.
- See the examples at http://shielded-mountain-6732.herokuapp.com to see this in action.
- """
-
- media_type = 'text/html'
- format = 'html'
- template = 'djangorestframework/api.html'
-
-
-class DocumentingXHTMLRenderer(DocumentingTemplateRenderer):
- """
- Identical to DocumentingHTMLRenderer, except with an xhtml media type.
- We need this to be listed in preference to xml in order to return HTML to WebKit based browsers,
- given their Accept headers.
- """
-
- media_type = 'application/xhtml+xml'
- format = 'xhtml'
- template = 'djangorestframework/api.html'
-
-
-class DocumentingPlainTextRenderer(DocumentingTemplateRenderer):
- """
- Renderer that serializes the object with the default renderer, but also provides plain-text
- documentation of the returned status and headers, and of the resource's name and description.
- Useful for browsing an API with command line tools.
- """
-
- media_type = 'text/plain'
- format = 'txt'
- template = 'djangorestframework/api.txt'
-
-
-DEFAULT_RENDERERS = (
- JSONRenderer,
- JSONPRenderer,
- DocumentingHTMLRenderer,
- DocumentingXHTMLRenderer,
- DocumentingPlainTextRenderer,
- XMLRenderer
-)
-
-if yaml:
- DEFAULT_RENDERERS += (YAMLRenderer, )
-else:
- YAMLRenderer = None
diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py
deleted file mode 100644
index 5e350268..00000000
--- a/djangorestframework/resources.py
+++ /dev/null
@@ -1,345 +0,0 @@
-from django import forms
-
-from djangorestframework.response import ErrorResponse
-from djangorestframework.serializer import Serializer
-from djangorestframework.utils import as_tuple
-
-
-class BaseResource(Serializer):
- """
- Base class for all Resource classes, which simply defines the interface
- they provide.
- """
- fields = None
- include = None
- exclude = None
-
- def __init__(self, view=None, depth=None, stack=[], **kwargs):
- super(BaseResource, self).__init__(depth, stack, **kwargs)
- self.view = view
- self.request = getattr(view, 'request', None)
-
- def validate_request(self, data, files=None):
- """
- Given the request content return the cleaned, validated content.
- Typically raises a :exc:`response.ErrorResponse` with status code 400
- (Bad Request) on failure.
- """
- return data
-
- def filter_response(self, obj):
- """
- Given the response content, filter it into a serializable object.
- """
- return self.serialize(obj)
-
-
-class Resource(BaseResource):
- """
- A Resource determines how a python object maps to some serializable data.
- Objects that a resource can act on include plain Python object instances,
- Django Models, and Django QuerySets.
- """
-
- # The model attribute refers to the Django Model which this Resource maps to.
- # (The Model's class, rather than an instance of the Model)
- model = None
-
- # By default the set of returned fields will be the set of:
- #
- # 0. All the fields on the model, excluding 'id'.
- # 1. All the properties on the model.
- # 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
- #
- # If you wish to override this behaviour,
- # you should explicitly set the fields attribute on your class.
- fields = None
-
-
-class FormResource(Resource):
- """
- Resource class that uses forms for validation.
- Also provides a :meth:`get_bound_form` method which may be used by some renderers.
-
- On calling :meth:`validate_request` this validator may set a :attr:`bound_form_instance` attribute on the
- view, which may be used by some renderers.
- """
-
- form = None
- """
- The :class:`Form` class that should be used for request validation.
- This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
- """
-
- allow_unknown_form_fields = False
- """
- Flag to check for unknown fields when validating a form. If set to false and
- we receive request data that is not expected by the form it raises an
- :exc:`response.ErrorResponse` with status code 400. If set to true, only
- expected fields are validated.
- """
-
- def validate_request(self, data, files=None):
- """
- Given some content as input return some cleaned, validated content.
- Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
-
- Validation is standard form validation, with an additional constraint that *no extra unknown fields* may be supplied
- if :attr:`self.allow_unknown_form_fields` is ``False``.
-
- On failure the :exc:`response.ErrorResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
- If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
- If the :obj:`'field-errors'` key exists it is a dict of ``{'field name as string': ['errors as strings', ...]}``.
- """
- return self._validate(data, files)
-
- def _validate(self, data, files, allowed_extra_fields=(), fake_data=None):
- """
- Wrapped by validate to hide the extra flags that are used in the implementation.
-
- allowed_extra_fields is a list of fields which are not defined by the form, but which we still
- expect to see on the input.
-
- fake_data is a string that should be used as an extra key, as a kludge to force .errors
- to be populated when an empty dict is supplied in `data`
- """
-
- # We'd like nice error messages even if no content is supplied.
- # Typically if an empty dict is given to a form Django will
- # return .is_valid() == False, but .errors == {}
- #
- # To get around this case we revalidate with some fake data.
- if fake_data:
- data[fake_data] = '_fake_data'
- allowed_extra_fields = tuple(allowed_extra_fields) + ('_fake_data',)
-
- bound_form = self.get_bound_form(data, files)
-
- if bound_form is None:
- return data
-
- self.view.bound_form_instance = bound_form
-
- data = data and data or {}
- files = files and files or {}
-
- seen_fields_set = set(data.keys())
- form_fields_set = set(bound_form.fields.keys())
- allowed_extra_fields_set = set(allowed_extra_fields)
-
- # In addition to regular validation we also ensure no additional fields are being passed in...
- unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set)
- unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh.
-
- # Check using both regular validation, and our stricter no additional fields rule
- if bound_form.is_valid() and (self.allow_unknown_form_fields or not unknown_fields):
- # Validation succeeded...
- cleaned_data = bound_form.cleaned_data
-
- # Add in any extra fields to the cleaned content...
- for key in (allowed_extra_fields_set & seen_fields_set) - set(cleaned_data.keys()):
- cleaned_data[key] = data[key]
-
- return cleaned_data
-
- # Validation failed...
- detail = {}
-
- if not bound_form.errors and not unknown_fields:
- # is_valid() was False, but errors was empty.
- # If we havn't already done so attempt revalidation with some fake data
- # to force django to give us an errors dict.
- if fake_data is None:
- return self._validate(data, files, allowed_extra_fields, '_fake_data')
-
- # If we've already set fake_dict and we're still here, fallback gracefully.
- detail = {u'errors': [u'No content was supplied.']}
-
- else:
- # Add any non-field errors
- if bound_form.non_field_errors():
- detail[u'errors'] = bound_form.non_field_errors()
-
- # Add standard field errors
- field_errors = dict(
- (key, map(unicode, val))
- for (key, val)
- in bound_form.errors.iteritems()
- if not key.startswith('__')
- )
-
- # Add any unknown field errors
- if not self.allow_unknown_form_fields:
- for key in unknown_fields:
- field_errors[key] = [u'This field does not exist.']
-
- if field_errors:
- detail[u'field_errors'] = field_errors
-
- # Return HTTP 400 response (BAD REQUEST)
- raise ErrorResponse(400, detail)
-
- def get_form_class(self, method=None):
- """
- Returns the form class used to validate this resource.
- """
- # A form on the view overrides a form on the resource.
- form = getattr(self.view, 'form', None) or self.form
-
- # Use the requested method or determine the request method
- if method is None and hasattr(self.view, 'request') and hasattr(self.view, 'method'):
- method = self.view.method
- elif method is None and hasattr(self.view, 'request'):
- method = self.view.request.method
-
- # A method form on the view or resource overrides the general case.
- # Method forms are attributes like `get_form` `post_form` `put_form`.
- if method:
- form = getattr(self, '%s_form' % method.lower(), form)
- form = getattr(self.view, '%s_form' % method.lower(), form)
-
- return form
-
- def get_bound_form(self, data=None, files=None, method=None):
- """
- Given some content return a Django form bound to that content.
- If form validation is turned off (:attr:`form` class attribute is :const:`None`) then returns :const:`None`.
- """
- form = self.get_form_class(method)
-
- if not form:
- return None
-
- if data is not None or files is not None:
- return form(data, files)
-
- return form()
-
-
-class ModelResource(FormResource):
- """
- Resource class that uses forms for validation and otherwise falls back to a model form if no form is set.
- Also provides a :meth:`get_bound_form` method which may be used by some renderers.
- """
-
- form = None
- """
- The form class that should be used for request validation.
- If set to :const:`None` then the default model form validation will be used.
-
- This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
- """
-
- model = None
- """
- The model class which this resource maps to.
-
- This can be overridden by a :attr:`model` attribute on the :class:`views.View`.
- """
-
- fields = None
- """
- The list of fields to use on the output.
-
- May be any of:
-
- The name of a model field. To view nested resources, give the field as a tuple of ("fieldName", resource) where `resource` may be any of ModelResource reference, the name of a ModelResourc reference as a string or a tuple of strings representing fields on the nested model.
- The name of an attribute on the model.
- The name of an attribute on the resource.
- The name of a method on the model, with a signature like ``func(self)``.
- The name of a method on the resource, with a signature like ``func(self, instance)``.
- """
-
- exclude = ('id', 'pk')
- """
- The list of fields to exclude. This is only used if :attr:`fields` is not set.
- """
-
- include = ()
- """
- The list of extra fields to include. This is only used if :attr:`fields` is not set.
- """
-
- def __init__(self, view=None, depth=None, stack=[], **kwargs):
- """
- Allow :attr:`form` and :attr:`model` attributes set on the
- :class:`View` to override the :attr:`form` and :attr:`model`
- attributes set on the :class:`Resource`.
- """
- super(ModelResource, self).__init__(view, depth, stack, **kwargs)
-
- self.model = getattr(view, 'model', None) or self.model
-
- def validate_request(self, data, files=None):
- """
- Given some content as input return some cleaned, validated content.
- Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
-
- Validation is standard form or model form validation,
- with an additional constraint that no extra unknown fields may be supplied,
- and that all fields specified by the fields class attribute must be supplied,
- even if they are not validated by the form/model form.
-
- On failure the ErrorResponse content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
- If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
- If the ''field-errors'` key exists it is a dict of {field name as string: list of errors as strings}.
- """
- return self._validate(data, files, allowed_extra_fields=self._property_fields_set)
-
- def get_bound_form(self, data=None, files=None, method=None):
- """
- Given some content return a ``Form`` instance bound to that content.
-
- If the :attr:`form` class attribute has been explicitly set then that class will be used
- to create the Form, otherwise the model will be used to create a ModelForm.
- """
- form = self.get_form_class(method)
-
- if not form and self.model:
- # Fall back to ModelForm which we create on the fly
- class OnTheFlyModelForm(forms.ModelForm):
- class Meta:
- model = self.model
- #fields = tuple(self._model_fields_set)
-
- form = OnTheFlyModelForm
-
- # Both form and model not set? Okay bruv, whatevs...
- if not form:
- return None
-
- # Instantiate the ModelForm as appropriate
- if data is not None or files is not None:
- if issubclass(form, forms.ModelForm) and hasattr(self.view, 'model_instance'):
- # Bound to an existing model instance
- return form(data, files, instance=self.view.model_instance)
- else:
- return form(data, files)
-
- return form()
-
- @property
- def _model_fields_set(self):
- """
- Return a set containing the names of validated fields on the model.
- """
- model_fields = set(field.name for field in self.model._meta.fields)
-
- if self.fields:
- return model_fields & set(as_tuple(self.fields))
-
- return model_fields - set(as_tuple(self.exclude))
-
- @property
- def _property_fields_set(self):
- """
- Returns a set containing the names of validated properties on the model.
- """
- property_fields = set(attr for attr in dir(self.model) if
- isinstance(getattr(self.model, attr, None), property)
- and not attr.startswith('_'))
-
- if self.fields:
- return property_fields & set(as_tuple(self.fields))
-
- return property_fields.union(set(as_tuple(self.include))) - set(as_tuple(self.exclude))
diff --git a/djangorestframework/response.py b/djangorestframework/response.py
deleted file mode 100644
index 13f0477e..00000000
--- a/djangorestframework/response.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-The :mod:`response` module provides Response classes you can use in your
-views to return a certain HTTP response. Typically a response is *rendered*
-into a HTTP response depending on what renderers are set on your view and
-als depending on the accept header of the request.
-"""
-
-from django.core.handlers.wsgi import STATUS_CODE_TEXT
-
-__all__ = ('Response', 'ErrorResponse')
-
-# TODO: remove raw_content/cleaned_content and just use content?
-
-
-class Response(object):
- """
- An HttpResponse that may include content that hasn't yet been serialized.
- """
-
- def __init__(self, status=200, content=None, headers=None):
- self.status = status
- self.media_type = None
- self.has_content_body = content is not None
- self.raw_content = content # content prior to filtering
- self.cleaned_content = content # content after filtering
- self.headers = headers or {}
-
- @property
- def status_text(self):
- """
- Return reason text corresponding to our HTTP response status code.
- Provided for convenience.
- """
- return STATUS_CODE_TEXT.get(self.status, '')
-
-
-class ErrorResponse(Exception):
- """
- An exception representing an Response that should be returned immediately.
- Any content should be serialized as-is, without being filtered.
- """
-
- def __init__(self, status, content=None, headers={}):
- self.response = Response(status, content=content, headers=headers)
diff --git a/djangorestframework/serializer.py b/djangorestframework/serializer.py
deleted file mode 100644
index d2349b53..00000000
--- a/djangorestframework/serializer.py
+++ /dev/null
@@ -1,300 +0,0 @@
-"""
-Customizable serialization.
-"""
-from django.db import models
-from django.db.models.query import QuerySet, RawQuerySet
-from django.utils.encoding import smart_unicode, is_protected_type, smart_str
-
-import inspect
-import types
-
-
-# We register serializer classes, so that we can refer to them by their
-# class names, if there are cyclical serialization heirachys.
-_serializers = {}
-
-
-def _field_to_tuple(field):
- """
- Convert an item in the `fields` attribute into a 2-tuple.
- """
- if isinstance(field, (tuple, list)):
- return (field[0], field[1])
- return (field, None)
-
-
-def _fields_to_list(fields):
- """
- Return a list of field tuples.
- """
- return [_field_to_tuple(field) for field in fields or ()]
-
-
-class _SkipField(Exception):
- """
- Signals that a serialized field should be ignored.
- We use this mechanism as the default behavior for ensuring
- that we don't infinitely recurse when dealing with nested data.
- """
- pass
-
-
-class _RegisterSerializer(type):
- """
- Metaclass to register serializers.
- """
- def __new__(cls, name, bases, attrs):
- # Build the class and register it.
- ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs)
- _serializers[name] = ret
- return ret
-
-
-class Serializer(object):
- """
- Converts python objects into plain old native types suitable for
- serialization. In particular it handles models and querysets.
-
- The output format is specified by setting a number of attributes
- on the class.
-
- You may also override any of the serialization methods, to provide
- for more flexible behavior.
-
- Valid output types include anything that may be directly rendered into
- json, xml etc...
- """
- __metaclass__ = _RegisterSerializer
-
- fields = ()
- """
- Specify the fields to be serialized on a model or dict.
- Overrides `include` and `exclude`.
- """
-
- include = ()
- """
- Fields to add to the default set to be serialized on a model/dict.
- """
-
- exclude = ()
- """
- Fields to remove from the default set to be serialized on a model/dict.
- """
-
- rename = {}
- """
- A dict of key->name to use for the field keys.
- """
-
- related_serializer = None
- """
- The default serializer class to use for any related models.
- """
-
- depth = None
- """
- The maximum depth to serialize to, or `None`.
- """
-
- parent = None
- """
- A reference to the root serializer when descending down into fields.
- """
-
- def __init__(self, depth=None, stack=[], **kwargs):
- if depth is not None:
- self.depth = depth
- self.stack = stack
-
- def get_fields(self, obj):
- fields = self.fields
-
- # If `fields` is not set, we use the default fields and modify
- # them with `include` and `exclude`
- if not fields:
- default = self.get_default_fields(obj)
- include = self.include or ()
- exclude = self.exclude or ()
- fields = set(default + list(include)) - set(exclude)
-
- return fields
-
- def get_default_fields(self, obj):
- """
- Return the default list of field names/keys for a model instance/dict.
- These are used if `fields` is not given.
- """
- if isinstance(obj, models.Model):
- opts = obj._meta
- return [field.name for field in opts.fields + opts.many_to_many]
- else:
- return obj.keys()
-
- def get_related_serializer(self, info):
- # If an element in `fields` is a 2-tuple of (str, tuple)
- # then the second element of the tuple is the fields to
- # set on the related serializer
-
- class OnTheFlySerializer(self.__class__):
- fields = info
- parent = getattr(self, 'parent') or self
-
- if isinstance(info, (list, tuple)):
- return OnTheFlySerializer
-
- # If an element in `fields` is a 2-tuple of (str, Serializer)
- # then the second element of the tuple is the Serializer
- # class to use for that field.
- elif isinstance(info, type) and issubclass(info, Serializer):
- return info
-
- # If an element in `fields` is a 2-tuple of (str, str)
- # then the second element of the tuple is the name of the Serializer
- # class to use for that field.
- #
- # Black magic to deal with cyclical Serializer dependancies.
- # Similar to what Django does for cyclically related models.
- elif isinstance(info, str) and info in _serializers:
- return _serializers[info]
-
- # Otherwise use `related_serializer` or fall back to
- # `OnTheFlySerializer` preserve custom serialization methods.
- return getattr(self, 'related_serializer') or OnTheFlySerializer
-
- def serialize_key(self, key):
- """
- Keys serialize to their string value,
- unless they exist in the `rename` dict.
- """
- return self.rename.get(smart_str(key), smart_str(key))
-
- def serialize_val(self, key, obj, related_info):
- """
- Convert a model field or dict value into a serializable representation.
- """
- related_serializer = self.get_related_serializer(related_info)
-
- if self.depth is None:
- depth = None
- elif self.depth <= 0:
- return self.serialize_max_depth(obj)
- else:
- depth = self.depth - 1
-
- if obj in self.stack:
- return self.serialize_recursion(obj)
- else:
- stack = self.stack[:]
- stack.append(obj)
-
- return related_serializer(depth=depth, stack=stack).serialize(
- obj, request=getattr(self, 'request', None))
-
- def serialize_max_depth(self, obj):
- """
- Determine how objects should be serialized once `depth` is exceeded.
- The default behavior is to ignore the field.
- """
- raise _SkipField
-
- def serialize_recursion(self, obj):
- """
- Determine how objects should be serialized if recursion occurs.
- The default behavior is to ignore the field.
- """
- raise _SkipField
-
- def serialize_model(self, instance):
- """
- Given a model instance or dict, serialize it to a dict..
- """
- data = {}
- # Append the instance itself to the stack so that you never iterate
- # back into the first object.
- self.stack.append(instance)
-
- fields = self.get_fields(instance)
-
- # serialize each required field
- for fname, related_info in _fields_to_list(fields):
- try:
- # we first check for a method 'fname' on self,
- # 'fname's signature must be 'def fname(self, instance)'
- meth = getattr(self, fname, None)
- if (inspect.ismethod(meth) and
- len(inspect.getargspec(meth)[0]) == 2):
- obj = meth(instance)
- elif hasattr(instance, '__contains__') and fname in instance:
- # then check for a key 'fname' on the instance
- obj = instance[fname]
- elif hasattr(instance, smart_str(fname)):
- # finally check for an attribute 'fname' on the instance
- obj = getattr(instance, fname)
- else:
- continue
-
- key = self.serialize_key(fname)
- val = self.serialize_val(fname, obj, related_info)
- data[key] = val
- except _SkipField:
- pass
-
- return data
-
- def serialize_iter(self, obj):
- """
- Convert iterables into a serializable representation.
- """
- return [self.serialize(item) for item in obj]
-
- def serialize_func(self, obj):
- """
- Convert no-arg methods and functions into a serializable representation.
- """
- return self.serialize(obj())
-
- def serialize_manager(self, obj):
- """
- Convert a model manager into a serializable representation.
- """
- return self.serialize_iter(obj.all())
-
- def serialize_fallback(self, obj):
- """
- Convert any unhandled object into a serializable representation.
- """
- return smart_unicode(obj, strings_only=True)
-
- def serialize(self, obj, request=None):
- """
- Convert any object into a serializable representation.
- """
-
- # Request from related serializer.
- if request is not None:
- self.request = request
-
- if isinstance(obj, (dict, models.Model)):
- # Model instances & dictionaries
- return self.serialize_model(obj)
- elif isinstance(obj, (tuple, list, set, QuerySet, RawQuerySet, types.GeneratorType)):
- # basic iterables
- return self.serialize_iter(obj)
- elif isinstance(obj, models.Manager):
- # Manager objects
- return self.serialize_manager(obj)
- elif inspect.isfunction(obj) and not inspect.getargspec(obj)[0]:
- # function with no args
- return self.serialize_func(obj)
- elif inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1:
- # bound method
- return self.serialize_func(obj)
-
- # Protected types are passed through as is.
- # (i.e. Primitives like None, numbers, dates, and Decimals.)
- if is_protected_type(obj):
- return obj
-
- # All other values are converted to string.
- return self.serialize_fallback(obj)
diff --git a/djangorestframework/static/djangorestframework/css/style.css b/djangorestframework/static/djangorestframework/css/style.css
deleted file mode 100644
index d47aa16a..00000000
--- a/djangorestframework/static/djangorestframework/css/style.css
+++ /dev/null
@@ -1,1209 +0,0 @@
-/********************** admin 'base.css' ************************/
-
-body {
- margin: 0;
- padding: 0;
- font-size: 12px;
- font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
- color: #333;
- background: #fff;
-}
-
-/* LINKS */
-
-a:link, a:visited {
- color: #5b80b2;
- text-decoration: none;
-}
-
-a:hover {
- color: #036;
-}
-
-a img {
- border: none;
-}
-
-a.section:link, a.section:visited {
- color: white;
- text-decoration: none;
-}
-
-/* GLOBAL DEFAULTS */
-
-p, ol, ul, dl {
- margin: .2em 0 .8em 0;
-}
-
-p {
- padding: 0;
- line-height: 140%;
-}
-
-h1,h2,h3,h4,h5 {
- font-weight: bold;
-}
-
-h1 {
- font-size: 18px;
- color: #666;
- padding: 0 6px 0 0;
- margin: 0 0 .2em 0;
-}
-
-h2 {
- font-size: 16px;
- margin: 1em 0 .5em 0;
-}
-
-h2.subhead {
- font-weight: normal;
- margin-top: 0;
-}
-
-h3 {
- font-size: 14px;
- margin: .8em 0 .3em 0;
- color: #666;
- font-weight: bold;
-}
-
-h4 {
- font-size: 12px;
- margin: 1em 0 .8em 0;
- padding-bottom: 3px;
-}
-
-h5 {
- font-size: 10px;
- margin: 1.5em 0 .5em 0;
- color: #666;
- text-transform: uppercase;
- letter-spacing: 1px;
-}
-
-ul li {
- list-style-type: square;
- padding: 1px 0;
-}
-
-ul.plainlist {
- margin-left: 0 !important;
-}
-
-ul.plainlist li {
- list-style-type: none;
-}
-
-li ul {
- margin-bottom: 0;
-}
-
-li, dt, dd {
- font-size: 11px;
- line-height: 14px;
-}
-
-dt {
- font-weight: bold;
- margin-top: 4px;
-}
-
-dd {
- margin-left: 0;
-}
-
-form {
- margin: 0;
- padding: 0;
-}
-
-fieldset {
- margin: 0;
- padding: 0;
-}
-
-blockquote {
- font-size: 11px;
- color: #777;
- margin-left: 2px;
- padding-left: 10px;
- border-left: 5px solid #ddd;
-}
-
-code, pre {
- font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
- background: inherit;
- color: #666;
- font-size: 11px;
-}
-
-pre.literal-block {
- margin: 10px;
- background: #eee;
- padding: 6px 8px;
-}
-
-code strong {
- color: #930;
-}
-
-hr {
- clear: both;
- color: #eee;
- background-color: #eee;
- height: 1px;
- border: none;
- margin: 0;
- padding: 0;
- font-size: 1px;
- line-height: 1px;
-}
-
-/* TEXT STYLES & MODIFIERS */
-
-.small {
- font-size: 11px;
-}
-
-.tiny {
- font-size: 10px;
-}
-
-p.tiny {
- margin-top: -2px;
-}
-
-.mini {
- font-size: 9px;
-}
-
-p.mini {
- margin-top: -3px;
-}
-
-.help, p.help {
- font-size: 10px !important;
- color: #999;
-}
-
-p img, h1 img, h2 img, h3 img, h4 img, td img {
- vertical-align: middle;
-}
-
-.quiet, a.quiet:link, a.quiet:visited {
- color: #999 !important;
- font-weight: normal !important;
-}
-
-.quiet strong {
- font-weight: bold !important;
-}
-
-.float-right {
- float: right;
-}
-
-.float-left {
- float: left;
-}
-
-.clear {
- clear: both;
-}
-
-.align-left {
- text-align: left;
-}
-
-.align-right {
- text-align: right;
-}
-
-.example {
- margin: 10px 0;
- padding: 5px 10px;
- background: #efefef;
-}
-
-.nowrap {
- white-space: nowrap;
-}
-
-/* TABLES */
-
-table {
- border-collapse: collapse;
- border-color: #ccc;
-}
-
-td, th {
- font-size: 11px;
- line-height: 13px;
- border-bottom: 1px solid #eee;
- vertical-align: top;
- padding: 5px;
- font-family: "Lucida Grande", Verdana, Arial, sans-serif;
-}
-
-th {
- text-align: left;
- font-size: 12px;
- font-weight: bold;
-}
-
-thead th,
-tfoot td {
- color: #666;
- padding: 2px 5px;
- font-size: 11px;
- background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x;
- border-left: 1px solid #ddd;
- border-bottom: 1px solid #ddd;
-}
-
-tfoot td {
- border-bottom: none;
- border-top: 1px solid #ddd;
-}
-
-thead th:first-child,
-tfoot td:first-child {
- border-left: none !important;
-}
-
-thead th.optional {
- font-weight: normal !important;
-}
-
-fieldset table {
- border-right: 1px solid #eee;
-}
-
-tr.row-label td {
- font-size: 9px;
- padding-top: 2px;
- padding-bottom: 0;
- border-bottom: none;
- color: #666;
- margin-top: -1px;
-}
-
-tr.alt {
- background: #f6f6f6;
-}
-
-.row1 {
- background: #EDF3FE;
-}
-
-.row2 {
- background: white;
-}
-
-/* SORTABLE TABLES */
-
-thead th a:link, thead th a:visited {
- color: #666;
- display: block;
-}
-
-table thead th.sorted {
- background-position: bottom left !important;
-}
-
-table thead th.sorted a {
- padding-right: 13px;
-}
-
-table thead th.ascending a {
- background: url(../../admin/img/admin/arrow-up.gif) right .4em no-repeat;
-}
-
-table thead th.descending a {
- background: url(../../admin/img/admin/arrow-down.gif) right .4em no-repeat;
-}
-
-/* ORDERABLE TABLES */
-
-table.orderable tbody tr td:hover {
- cursor: move;
-}
-
-table.orderable tbody tr td:first-child {
- padding-left: 14px;
- background-image: url(../../admin/img/admin/nav-bg-grabber.gif);
- background-repeat: repeat-y;
-}
-
-table.orderable-initalized .order-cell, body>tr>td.order-cell {
- display: none;
-}
-
-/* FORM DEFAULTS */
-
-input, textarea, select, .form-row p {
- margin: 2px 0;
- padding: 2px 3px;
- vertical-align: middle;
- font-family: "Lucida Grande", Verdana, Arial, sans-serif;
- font-weight: normal;
- font-size: 11px;
-}
-
-textarea {
- vertical-align: top !important;
-}
-
-input[type=text], input[type=password], textarea, select, .vTextField {
- border: 1px solid #ccc;
-}
-
-/* FORM BUTTONS */
-
-.button, input[type=submit], input[type=button], .submit-row input {
- background: white url(../../admin/img/admin/nav-bg.gif) bottom repeat-x;
- padding: 3px 5px;
- color: black;
- border: 1px solid #bbb;
- border-color: #ddd #aaa #aaa #ddd;
-}
-
-.button:active, input[type=submit]:active, input[type=button]:active {
- background-image: url(../../admin/img/admin/nav-bg-reverse.gif);
- background-position: top;
-}
-
-.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
- background-image: url(../../admin/img/admin/nav-bg.gif);
- background-position: bottom;
- opacity: 0.4;
-}
-
-.button.default, input[type=submit].default, .submit-row input.default {
- border: 2px solid #5b80b2;
- background: #7CA0C7 url(../../admin/img/admin/default-bg.gif) bottom repeat-x;
- font-weight: bold;
- color: white;
- float: right;
-}
-
-.button.default:active, input[type=submit].default:active {
- background-image: url(../../admin/img/admin/default-bg-reverse.gif);
- background-position: top;
-}
-
-.button[disabled].default, input[type=submit][disabled].default, input[type=button][disabled].default {
- background-image: url(../../admin/img/admin/default-bg.gif);
- background-position: bottom;
- opacity: 0.4;
-}
-
-
-/* MODULES */
-
-.module {
- border: 1px solid #ccc;
- margin-bottom: 5px;
- background: white;
-}
-
-.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
- padding-left: 10px;
- padding-right: 10px;
-}
-
-.module blockquote {
- margin-left: 12px;
-}
-
-.module ul, .module ol {
- margin-left: 1.5em;
-}
-
-.module h3 {
- margin-top: .6em;
-}
-
-.module h2, .module caption, .inline-group h2 {
- margin: 0;
- padding: 2px 5px 3px 5px;
- font-size: 11px;
- text-align: left;
- font-weight: bold;
- background: #7CA0C7 url(../../admin/img/admin/default-bg.gif) top left repeat-x;
- color: white;
-}
-
-.module table {
- border-collapse: collapse;
-}
-
-/* MESSAGES & ERRORS */
-
-ul.messagelist {
- padding: 0 0 5px 0;
- margin: 0;
-}
-
-ul.messagelist li {
- font-size: 12px;
- display: block;
- padding: 4px 5px 4px 25px;
- margin: 0 0 3px 0;
- border-bottom: 1px solid #ddd;
- color: #666;
- background: #ffc url(../../admin/img/admin/icon_success.gif) 5px .3em no-repeat;
-}
-
-ul.messagelist li.warning{
- background-image: url(../../admin/img/admin/icon_alert.gif);
-}
-
-ul.messagelist li.error{
- background-image: url(../../admin/img/admin/icon_error.gif);
-}
-
-.errornote {
- font-size: 12px !important;
- display: block;
- padding: 4px 5px 4px 25px;
- margin: 0 0 3px 0;
- border: 1px solid red;
- color: red;
- background: #ffc url(../../admin/img/admin/icon_error.gif) 5px .3em no-repeat;
-}
-
-ul.errorlist {
- margin: 0 !important;
- padding: 0 !important;
-}
-
-.errorlist li {
- font-size: 12px !important;
- display: block;
- padding: 4px 5px 4px 25px;
- margin: 0 0 3px 0;
- border: 1px solid red;
- color: white;
- background: red url(../../admin/img/admin/icon_alert.gif) 5px .3em no-repeat;
-}
-
-.errorlist li a {
- color: white;
- text-decoration: underline;
-}
-
-td ul.errorlist {
- margin: 0 !important;
- padding: 0 !important;
-}
-
-td ul.errorlist li {
- margin: 0 !important;
-}
-
-.errors {
- background: #ffc;
-}
-
-.errors input, .errors select, .errors textarea {
- border: 1px solid red;
-}
-
-div.system-message {
- background: #ffc;
- margin: 10px;
- padding: 6px 8px;
- font-size: .8em;
-}
-
-div.system-message p.system-message-title {
- padding: 4px 5px 4px 25px;
- margin: 0;
- color: red;
- background: #ffc url(../../admin/img/admin/icon_error.gif) 5px .3em no-repeat;
-}
-
-.description {
- font-size: 12px;
- padding: 5px 0 0 12px;
-}
-
-/* BREADCRUMBS */
-
-div.breadcrumbs {
- background: white url(../../admin/img/admin/nav-bg-reverse.gif) 0 -10px repeat-x;
- padding: 2px 8px 3px 8px;
- font-size: 11px;
- color: #999;
- border-top: 1px solid white;
- border-bottom: 1px solid #ccc;
- text-align: left;
-}
-
-/* ACTION ICONS */
-
-.addlink {
- padding-left: 12px;
- background: url(../../admin/img/admin/icon_addlink.gif) 0 .2em no-repeat;
-}
-
-.changelink {
- padding-left: 12px;
- background: url(../../admin/img/admin/icon_changelink.gif) 0 .2em no-repeat;
-}
-
-.deletelink {
- padding-left: 12px;
- background: url(../../admin/img/admin/icon_deletelink.gif) 0 .25em no-repeat;
-}
-
-a.deletelink:link, a.deletelink:visited {
- color: #CC3434;
-}
-
-a.deletelink:hover {
- color: #993333;
-}
-
-/* OBJECT TOOLS */
-
-.object-tools {
- font-size: 10px;
- font-weight: bold;
- font-family: Arial,Helvetica,sans-serif;
- padding-left: 0;
- float: right;
- position: relative;
- margin-top: -2.4em;
- margin-bottom: -2em;
-}
-
-.form-row .object-tools {
- margin-top: 5px;
- margin-bottom: 5px;
- float: none;
- height: 2em;
- padding-left: 3.5em;
-}
-
-.object-tools li {
- display: block;
- float: left;
- background: url(../../admin/img/admin/tool-left.gif) 0 0 no-repeat;
- padding: 0 0 0 8px;
- margin-left: 2px;
- height: 16px;
-}
-
-.object-tools li:hover {
- background: url(../../admin/img/admin/tool-left_over.gif) 0 0 no-repeat;
-}
-
-.object-tools a:link, .object-tools a:visited {
- display: block;
- float: left;
- color: white;
- padding: .1em 14px .1em 8px;
- height: 14px;
- background: #999 url(../../admin/img/admin/tool-right.gif) 100% 0 no-repeat;
-}
-
-.object-tools a:hover, .object-tools li:hover a {
- background: #5b80b2 url(../../admin/img/admin/tool-right_over.gif) 100% 0 no-repeat;
-}
-
-.object-tools a.viewsitelink, .object-tools a.golink {
- background: #999 url(../../admin/img/admin/tooltag-arrowright.gif) top right no-repeat;
- padding-right: 28px;
-}
-
-.object-tools a.viewsitelink:hover, .object-tools a.golink:hover {
- background: #5b80b2 url(../../admin/img/admin/tooltag-arrowright_over.gif) top right no-repeat;
-}
-
-.object-tools a.addlink {
- background: #999 url(../../admin/img/admin/tooltag-add.gif) top right no-repeat;
- padding-right: 28px;
-}
-
-.object-tools a.addlink:hover {
- background: #5b80b2 url(../../admin/img/admin/tooltag-add_over.gif) top right no-repeat;
-}
-
-/* OBJECT HISTORY */
-
-table#change-history {
- width: 100%;
-}
-
-table#change-history tbody th {
- width: 16em;
-}
-
-/* PAGE STRUCTURE */
-
-#container {
- position: relative;
- width: 100%;
- min-width: 760px;
- padding: 0;
-}
-
-#content {
- margin: 10px 15px;
-}
-
-#header {
- width: 100%;
-}
-
-#content-main {
- float: left;
- width: 100%;
-}
-
-#content-related {
- float: right;
- width: 18em;
- position: relative;
- margin-right: -19em;
-}
-
-#footer {
- clear: both;
- padding: 10px;
-}
-
-/* COLUMN TYPES */
-
-.colMS {
- margin-right: 20em !important;
-}
-
-.colSM {
- margin-left: 20em !important;
-}
-
-.colSM #content-related {
- float: left;
- margin-right: 0;
- margin-left: -19em;
-}
-
-.colSM #content-main {
- float: right;
-}
-
-.popup .colM {
- width: 95%;
-}
-
-.subcol {
- float: left;
- width: 46%;
- margin-right: 15px;
-}
-
-.dashboard #content {
- width: 500px;
-}
-
-/* HEADER */
-
-#header {
- background: #417690;
- color: #ffc;
- overflow: hidden;
-}
-
-#header a:link, #header a:visited {
- color: white;
-}
-
-#header a:hover {
- text-decoration: underline;
-}
-
-#branding h1 {
- padding: 0 10px;
- font-size: 18px;
- margin: 8px 0;
- font-weight: normal;
- color: #f4f379;
-}
-
-#branding h2 {
- padding: 0 10px;
- font-size: 14px;
- margin: -8px 0 8px 0;
- font-weight: normal;
- color: #ffc;
-}
-
-#user-tools {
- position: absolute;
- top: 0;
- right: 0;
- padding: 1.2em 10px;
- font-size: 11px;
- text-align: right;
-}
-
-/* SIDEBAR */
-
-#content-related h3 {
- font-size: 12px;
- color: #666;
- margin-bottom: 3px;
-}
-
-#content-related h4 {
- font-size: 11px;
-}
-
-#content-related .module h2 {
- background: #eee url(../../admin/img/admin/nav-bg.gif) bottom left repeat-x;
- color: #666;
-}
-
-/********************** admin 'forms.css' ************************/
-
-/* FORM ROWS */
-
-.form-row {
- overflow: hidden;
- padding: 8px 12px;
- font-size: 11px;
- border-bottom: 1px solid #eee;
-}
-
-.form-row img, .form-row input {
- vertical-align: middle;
-}
-
-form .form-row p {
- padding-left: 0;
- font-size: 11px;
-}
-
-/* FORM LABELS */
-
-form h4 {
- margin: 0 !important;
- padding: 0 !important;
- border: none !important;
-}
-
-label {
- font-weight: normal !important;
- color: #666;
- font-size: 12px;
-}
-
-.required label, label.required {
- font-weight: bold !important;
- color: #333 !important;
-}
-
-/* RADIO BUTTONS */
-
-form ul.radiolist li {
- list-style-type: none;
-}
-
-form ul.radiolist label {
- float: none;
- display: inline;
-}
-
-form ul.inline {
- margin-left: 0;
- padding: 0;
-}
-
-form ul.inline li {
- float: left;
- padding-right: 7px;
-}
-
-/* ALIGNED FIELDSETS */
-
-.aligned label {
- display: block;
- padding: 3px 10px 0 0;
- float: left;
- width: 8em;
-}
-
-.aligned ul label {
- display: inline;
- float: none;
- width: auto;
-}
-
-.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
- width: 350px;
-}
-
-form .aligned p, form .aligned ul {
- margin-left: 7em;
- padding-left: 30px;
-}
-
-form .aligned table p {
- margin-left: 0;
- padding-left: 0;
-}
-
-form .aligned p.help {
- padding-left: 38px;
-}
-
-.aligned .vCheckboxLabel {
- float: none !important;
- display: inline;
- padding-left: 4px;
-}
-
-.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
- width: 610px;
-}
-
-.checkbox-row p.help {
- margin-left: 0;
- padding-left: 0 !important;
-}
-
-fieldset .field-box {
- float: left;
- margin-right: 20px;
-}
-
-/* WIDE FIELDSETS */
-
-.wide label {
- width: 15em !important;
-}
-
-form .wide p {
- margin-left: 15em;
-}
-
-form .wide p.help {
- padding-left: 38px;
-}
-
-.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
- width: 450px;
-}
-
-/* COLLAPSED FIELDSETS */
-
-fieldset.collapsed * {
- display: none;
-}
-
-fieldset.collapsed h2, fieldset.collapsed {
- display: block !important;
-}
-
-fieldset.collapsed h2 {
- background-image: url(../../admin/img/admin/nav-bg.gif);
- background-position: bottom left;
- color: #999;
-}
-
-fieldset.collapsed .collapse-toggle {
- background: transparent;
- display: inline !important;
-}
-
-/* MONOSPACE TEXTAREAS */
-
-fieldset.monospace textarea {
- font-family: "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace;
-}
-
-/* SUBMIT ROW */
-
-.submit-row {
- padding: 5px 7px;
- text-align: right;
- background: white url(../../admin/img/admin/nav-bg.gif) 0 100% repeat-x;
- border: 1px solid #ccc;
- margin: 5px 0;
- overflow: hidden;
-}
-
-.submit-row input {
- margin: 0 0 0 5px;
-}
-
-.submit-row p {
- margin: 0.3em;
-}
-
-.submit-row p.deletelink-box {
- float: left;
-}
-
-.submit-row .deletelink {
- background: url(../../admin/img/admin/icon_deletelink.gif) 0 50% no-repeat;
- padding-left: 14px;
-}
-
-/* CUSTOM FORM FIELDS */
-
-.vSelectMultipleField {
- vertical-align: top !important;
-}
-
-.vCheckboxField {
- border: none;
-}
-
-.vDateField, .vTimeField {
- margin-right: 2px;
-}
-
-.vURLField {
- width: 30em;
-}
-
-.vLargeTextField, .vXMLLargeTextField {
- width: 48em;
-}
-
-.flatpages-flatpage #id_content {
- height: 40.2em;
-}
-
-.module table .vPositiveSmallIntegerField {
- width: 2.2em;
-}
-
-.vTextField {
- width: 20em;
-}
-
-.vIntegerField {
- width: 5em;
-}
-
-.vForeignKeyRawIdAdminField {
- width: 5em;
-}
-
-/* INLINES */
-
-.inline-group {
- padding: 0;
- border: 1px solid #ccc;
- margin: 10px 0;
-}
-
-.inline-group .aligned label {
- width: 8em;
-}
-
-.inline-related {
- position: relative;
-}
-
-.inline-related h3 {
- margin: 0;
- color: #666;
- padding: 3px 5px;
- font-size: 11px;
- background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x;
- border-bottom: 1px solid #ddd;
-}
-
-.inline-related h3 span.delete {
- float: right;
-}
-
-.inline-related h3 span.delete label {
- margin-left: 2px;
- font-size: 11px;
-}
-
-.inline-related fieldset {
- margin: 0;
- background: #fff;
- border: none;
-}
-
-.inline-related fieldset.module h3 {
- margin: 0;
- padding: 2px 5px 3px 5px;
- font-size: 11px;
- text-align: left;
- font-weight: bold;
- background: #bcd;
- color: #fff;
-}
-
-.inline-group .tabular fieldset.module {
- border: none;
- border-bottom: 1px solid #ddd;
-}
-
-.inline-related.tabular fieldset.module table {
- width: 100%;
-}
-
-.last-related fieldset {
- border: none;
-}
-
-.inline-group .tabular tr.has_original td {
- padding-top: 2em;
-}
-
-.inline-group .tabular tr td.original {
- padding: 2px 0 0 0;
- width: 0;
- _position: relative;
-}
-
-.inline-group .tabular th.original {
- width: 0px;
- padding: 0;
-}
-
-.inline-group .tabular td.original p {
- position: absolute;
- left: 0;
- height: 1.1em;
- padding: 2px 7px;
- overflow: hidden;
- font-size: 9px;
- font-weight: bold;
- color: #666;
- _width: 700px;
-}
-
-.inline-group ul.tools {
- padding: 0;
- margin: 0;
- list-style: none;
-}
-
-.inline-group ul.tools li {
- display: inline;
- padding: 0 5px;
-}
-
-.inline-group div.add-row,
-.inline-group .tabular tr.add-row td {
- color: #666;
- padding: 3px 5px;
- border-bottom: 1px solid #ddd;
- background: #e1e1e1 url(../../admin/img/admin/nav-bg.gif) top left repeat-x;
-}
-
-.inline-group .tabular tr.add-row td {
- padding: 4px 5px 3px;
- border-bottom: none;
-}
-
-.inline-group ul.tools a.add,
-.inline-group div.add-row a,
-.inline-group .tabular tr.add-row td a {
- background: url(../../admin/img/admin/icon_addlink.gif) 0 50% no-repeat;
- padding-left: 14px;
- font-size: 11px;
- outline: 0; /* Remove dotted border around link */
-}
-
-.empty-form {
- display: none;
-}
-
-/* IE7 specific bug fixes */
-
-.submit-row input {
- float: right;
-}
-
-body.login {
- background: #eee;
-}
-
-.login #container {
- background: white;
- border: 1px solid #ccc;
- width: 28em;
- min-width: 300px;
- margin-left: auto;
- margin-right: auto;
- margin-top: 100px;
-}
-
-.login #content-main {
- width: 100%;
-}
-
-.login form {
- margin-top: 1em;
-}
-
-.login .form-row {
- padding: 4px 0;
- float: left;
- width: 100%;
-}
-
-.login .form-row label {
- float: left;
- width: 9em;
- padding-right: 0.5em;
- line-height: 2em;
- text-align: right;
- font-size: 1em;
- color: #333;
-}
-
-.login .form-row #id_username, .login .form-row #id_password {
- width: 14em;
-}
-
-.login span.help {
- font-size: 10px;
- display: block;
-}
-
-.login .submit-row {
- clear: both;
- padding: 1em 0 0 9.4em;
-}
-
-/* Overrides specific to REST framework */
-
-#site-name a {
- color: #F4F379 !important;
-}
-
-.errorlist {
- display: inline !important;
-}
-
-.errorlist li {
- display: inline !important;
- background: white !important;
- color: black !important;
- border: 0 !important;
-}
-
-/* Custom styles */
-
-.version {
- font-size: 8px;
-}
-
-.form-row {
- border-bottom: 0.25em !important;
-}
diff --git a/djangorestframework/templates/djangorestframework/api.txt b/djangorestframework/templates/djangorestframework/api.txt
deleted file mode 100644
index b584952c..00000000
--- a/djangorestframework/templates/djangorestframework/api.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-{% autoescape off %}{{ name }}
-
-{{ description }}
-
-HTTP/1.0 {{ response.status }} {{ response.status_text }}
-{% for key, val in response.headers.items %}{{ key }}: {{ val }}
-{% endfor %}
-{{ content }}{% endautoescape %}
diff --git a/djangorestframework/templates/djangorestframework/base.html b/djangorestframework/templates/djangorestframework/base.html
deleted file mode 100644
index 2bc988de..00000000
--- a/djangorestframework/templates/djangorestframework/base.html
+++ /dev/null
@@ -1,150 +0,0 @@
-{% load url from future %}
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-{% load urlize_quoted_links %}
-{% load add_query_param %}
-{% load static %}
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/style.css'/>
- {% block extrastyle %}{% endblock %}
- <title>{% block title %}Django REST framework - {{ name }}{% endblock %}</title>
- {% block extrahead %}{% endblock %}
- {% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
- </head>
- <body class="{% block bodyclass %}{% endblock %}">
- <div id="container">
-
- <div id="header">
- <div id="branding">
- <h1 id="site-name">{% block branding %}<a href='http://django-rest-framework.org'>Django REST framework</a> <span class="version"> v {{ version }}</span>{% endblock %}</h1>
- </div>
- <div id="user-tools">
- {% block userlinks %}
- {% if user.is_active %}
- Welcome, {{ user }}.
- <a href='{% url 'djangorestframework:logout' %}?next={{ request.path }}'>Log out</a>
- {% else %}
- Anonymous
- <a href='{% url 'djangorestframework:login' %}?next={{ request.path }}'>Log in</a>
- {% endif %}
- {% endblock %}
- </div>
- {% block nav-global %}{% endblock %}
- </div>
-
- <div class="breadcrumbs">
- {% block breadcrumbs %}
- {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
- <a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a> {% if not forloop.last %}&rsaquo;{% endif %}
- {% endfor %}
- {% endblock %}
- </div>
-
- <!-- Content -->
- <div id="content" class="{% block coltype %}colM{% endblock %}">
-
- {% if 'OPTIONS' in view.allowed_methods %}
- <form action="{{ request.get_full_path }}" method="post">
- {% csrf_token %}
- <input type="hidden" name="{{ METHOD_PARAM }}" value="OPTIONS" />
- <input type="submit" value="OPTIONS" class="default" />
- </form>
- {% endif %}
-
- <div class='content-main'>
- <h1>{{ name }}</h1>
- <p>{{ description }}</p>
- <div class='module'>
- <pre><b>{{ response.status }} {{ response.status_text }}</b>{% autoescape off %}
-{% for key, val in response.headers.items %}<b>{{ key }}:</b> {{ val|urlize_quoted_links }}
-{% endfor %}
-{{ content|urlize_quoted_links }}</pre>{% endautoescape %}</div>
-
- {% if 'GET' in view.allowed_methods %}
- <form>
- <fieldset class='module aligned'>
- <h2>GET {{ name }}</h2>
- <div class='submit-row' style='margin: 0; border: 0'>
- <a href='{{ request.get_full_path }}' rel="nofollow" style='float: left'>GET</a>
- {% for format in available_formats %}
- {% with FORMAT_PARAM|add:"="|add:format as param %}
- [<a href='{{ request.get_full_path|add_query_param:param }}' rel="nofollow">{{ format }}</a>]
- {% endwith %}
- {% endfor %}
- </div>
- </fieldset>
- </form>
- {% endif %}
-
- {# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled and the user has permissions on this view. #}
- {% if METHOD_PARAM and response.status != 403 %}
-
- {% if 'POST' in view.allowed_methods %}
- <form action="{{ request.get_full_path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
- <fieldset class='module aligned'>
- <h2>POST {{ name }}</h2>
- {% csrf_token %}
- {{ post_form.non_field_errors }}
- {% for field in post_form %}
- <div class='form-row'>
- {{ field.label_tag }}
- {{ field }}
- <span class='help'>{{ field.help_text }}</span>
- {{ field.errors }}
- </div>
- {% endfor %}
- <div class='submit-row' style='margin: 0; border: 0'>
- <input type="submit" value="POST" class="default" />
- </div>
- </fieldset>
- </form>
- {% endif %}
-
- {% if 'PUT' in view.allowed_methods %}
- <form action="{{ request.get_full_path }}" method="post" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
- <fieldset class='module aligned'>
- <h2>PUT {{ name }}</h2>
- <input type="hidden" name="{{ METHOD_PARAM }}" value="PUT" />
- {% csrf_token %}
- {{ put_form.non_field_errors }}
- {% for field in put_form %}
- <div class='form-row'>
- {{ field.label_tag }}
- {{ field }}
- <span class='help'>{{ field.help_text }}</span>
- {{ field.errors }}
- </div>
- {% endfor %}
- <div class='submit-row' style='margin: 0; border: 0'>
- <input type="submit" value="PUT" class="default" />
- </div>
- </fieldset>
- </form>
- {% endif %}
-
- {% if 'DELETE' in view.allowed_methods %}
- <form action="{{ request.get_full_path }}" method="post">
- <fieldset class='module aligned'>
- <h2>DELETE {{ name }}</h2>
- {% csrf_token %}
- <input type="hidden" name="{{ METHOD_PARAM }}" value="DELETE" />
- <div class='submit-row' style='margin: 0; border: 0'>
- <input type="submit" value="DELETE" class="default" />
- </div>
- </fieldset>
- </form>
- {% endif %}
-
- {% endif %}
- </div>
- <!-- END content-main -->
-
- </div>
- <!-- END Content -->
-
- {% block footer %}<div id="footer"></div>{% endblock %}
- </div>
- </body>
-</html>
diff --git a/djangorestframework/templates/djangorestframework/login.html b/djangorestframework/templates/djangorestframework/login.html
deleted file mode 100644
index ce14db5b..00000000
--- a/djangorestframework/templates/djangorestframework/login.html
+++ /dev/null
@@ -1,45 +0,0 @@
-{% load static %}
-{% load url from future %}
-<html>
-
- <head>
- <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}djangorestframework/css/style.css'/>
- </head>
-
- <body class="login">
-
- <div id="container">
-
- <div id="header">
- <div id="branding">
- <h1 id="site-name">Django REST framework</h1>
- </div>
- </div>
-
- <div id="content" class="colM">
- <div id="content-main">
- <form method="post" action="{% url 'djangorestframework:login' %}" id="login-form">
- {% csrf_token %}
- <div class="form-row">
- <label for="id_username">Username:</label> {{ form.username }}
- </div>
- <div class="form-row">
- <label for="id_password">Password:</label> {{ form.password }}
- <input type="hidden" name="next" value="{{ next }}" />
- </div>
- <div class="form-row">
- <label>&nbsp;</label><input type="submit" value="Log in">
- </div>
- </form>
- <script type="text/javascript">
- document.getElementById('id_username').focus()
- </script>
- </div>
- <br class="clear">
- </div>
-
- <div id="footer"></div>
-
- </div>
- </body>
-</html>
diff --git a/djangorestframework/templatetags/add_query_param.py b/djangorestframework/templatetags/add_query_param.py
deleted file mode 100644
index 143d7b3f..00000000
--- a/djangorestframework/templatetags/add_query_param.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.template import Library
-from urlobject import URLObject
-register = Library()
-
-
-def add_query_param(url, param):
- return unicode(URLObject(url).add_query_param(*param.split('=')))
-
-
-register.filter('add_query_param', add_query_param) \ No newline at end of file
diff --git a/djangorestframework/tests/__init__.py b/djangorestframework/tests/__init__.py
deleted file mode 100644
index f664c5c1..00000000
--- a/djangorestframework/tests/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""Force import of all modules in this package in order to get the standard test runner to pick up the tests. Yowzers."""
-import os
-
-modules = [filename.rsplit('.', 1)[0]
- for filename in os.listdir(os.path.dirname(__file__))
- if filename.endswith('.py') and not filename.startswith('_')]
-__test__ = dict()
-
-for module in modules:
- exec("from djangorestframework.tests.%s import __doc__ as module_doc" % module)
- exec("from djangorestframework.tests.%s import *" % module)
- __test__[module] = module_doc or ""
-
diff --git a/djangorestframework/tests/accept.py b/djangorestframework/tests/accept.py
deleted file mode 100644
index 7f4eb320..00000000
--- a/djangorestframework/tests/accept.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from django.conf.urls.defaults import patterns, url, include
-from django.test import TestCase
-from djangorestframework.compat import RequestFactory
-from djangorestframework.views import View
-
-
-# See: http://www.useragentstring.com/
-MSIE_9_USER_AGENT = 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'
-MSIE_8_USER_AGENT = 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)'
-MSIE_7_USER_AGENT = 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)'
-FIREFOX_4_0_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)'
-CHROME_11_0_USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17'
-SAFARI_5_0_USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-ca) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/531.2+'
-OPERA_11_0_MSIE_USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00'
-OPERA_11_0_OPERA_USER_AGENT = 'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00'
-
-
-urlpatterns = patterns('',
- url(r'^api', include('djangorestframework.urls', namespace='djangorestframework'))
-)
-
-
-class UserAgentMungingTest(TestCase):
- """
- We need to fake up the accept headers when we deal with MSIE. Blergh.
- http://www.gethifi.com/blog/browser-rest-http-accept-headers
- """
-
- urls = 'djangorestframework.tests.accept'
-
- def setUp(self):
-
- class MockView(View):
- permissions = ()
-
- def get(self, request):
- return {'a':1, 'b':2, 'c':3}
-
- self.req = RequestFactory()
- self.MockView = MockView
- self.view = MockView.as_view()
-
- def test_munge_msie_accept_header(self):
- """Send MSIE user agent strings and ensure that we get an HTML response,
- even if we set a */* accept header."""
- for user_agent in (MSIE_9_USER_AGENT,
- MSIE_8_USER_AGENT,
- MSIE_7_USER_AGENT):
- req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
- resp = self.view(req)
- self.assertEqual(resp['Content-Type'], 'text/html')
-
- def test_dont_munge_msie_with_x_requested_with_header(self):
- """Send MSIE user agent strings, and an X-Requested-With header, and
- ensure that we get a JSON response if we set a */* Accept header."""
- for user_agent in (MSIE_9_USER_AGENT,
- MSIE_8_USER_AGENT,
- MSIE_7_USER_AGENT):
- req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- resp = self.view(req)
- self.assertEqual(resp['Content-Type'], 'application/json')
-
- def test_dont_rewrite_msie_accept_header(self):
- """Turn off _IGNORE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure
- that we get a JSON response if we set a */* accept header."""
- view = self.MockView.as_view(_IGNORE_IE_ACCEPT_HEADER=False)
-
- for user_agent in (MSIE_9_USER_AGENT,
- MSIE_8_USER_AGENT,
- MSIE_7_USER_AGENT):
- req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
- resp = view(req)
- self.assertEqual(resp['Content-Type'], 'application/json')
-
- def test_dont_munge_nice_browsers_accept_header(self):
- """Send Non-MSIE user agent strings and ensure that we get a JSON response,
- if we set a */* Accept header. (Other browsers will correctly set the Accept header)"""
- for user_agent in (FIREFOX_4_0_USER_AGENT,
- CHROME_11_0_USER_AGENT,
- SAFARI_5_0_USER_AGENT,
- OPERA_11_0_MSIE_USER_AGENT,
- OPERA_11_0_OPERA_USER_AGENT):
- req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
- resp = self.view(req)
- self.assertEqual(resp['Content-Type'], 'application/json')
-
diff --git a/djangorestframework/tests/content.py b/djangorestframework/tests/content.py
deleted file mode 100644
index e5d98e32..00000000
--- a/djangorestframework/tests/content.py
+++ /dev/null
@@ -1,233 +0,0 @@
-"""
-Tests for content parsing, and form-overloaded content parsing.
-"""
-from django.conf.urls.defaults import patterns
-from django.contrib.auth.models import User
-from django.test import TestCase, Client
-from djangorestframework import status
-from djangorestframework.authentication import UserLoggedInAuthentication
-from djangorestframework.compat import RequestFactory, unittest
-from djangorestframework.mixins import RequestMixin
-from djangorestframework.parsers import FormParser, MultiPartParser, \
- PlainTextParser, JSONParser
-from djangorestframework.response import Response
-from djangorestframework.views import View
-
-class MockView(View):
- authentication = (UserLoggedInAuthentication,)
- def post(self, request):
- if request.POST.get('example') is not None:
- return Response(status.HTTP_200_OK)
-
- return Response(status.INTERNAL_SERVER_ERROR)
-
-urlpatterns = patterns('',
- (r'^$', MockView.as_view()),
-)
-
-class TestContentParsing(TestCase):
- def setUp(self):
- self.req = RequestFactory()
-
- def ensure_determines_no_content_GET(self, view):
- """Ensure view.DATA returns None for GET request with no content."""
- view.request = self.req.get('/')
- self.assertEqual(view.DATA, None)
-
- def ensure_determines_no_content_HEAD(self, view):
- """Ensure view.DATA returns None for HEAD request."""
- view.request = self.req.head('/')
- self.assertEqual(view.DATA, None)
-
- def ensure_determines_form_content_POST(self, view):
- """Ensure view.DATA returns content for POST request with form content."""
- form_data = {'qwerty': 'uiop'}
- view.parsers = (FormParser, MultiPartParser)
- view.request = self.req.post('/', data=form_data)
- self.assertEqual(view.DATA.items(), form_data.items())
-
- def ensure_determines_non_form_content_POST(self, view):
- """Ensure view.RAW_CONTENT returns content for POST request with non-form content."""
- content = 'qwerty'
- content_type = 'text/plain'
- view.parsers = (PlainTextParser,)
- view.request = self.req.post('/', content, content_type=content_type)
- self.assertEqual(view.DATA, content)
-
- def ensure_determines_form_content_PUT(self, view):
- """Ensure view.RAW_CONTENT returns content for PUT request with form content."""
- form_data = {'qwerty': 'uiop'}
- view.parsers = (FormParser, MultiPartParser)
- view.request = self.req.put('/', data=form_data)
- self.assertEqual(view.DATA.items(), form_data.items())
-
- def ensure_determines_non_form_content_PUT(self, view):
- """Ensure view.RAW_CONTENT returns content for PUT request with non-form content."""
- content = 'qwerty'
- content_type = 'text/plain'
- view.parsers = (PlainTextParser,)
- view.request = self.req.post('/', content, content_type=content_type)
- self.assertEqual(view.DATA, content)
-
- def test_standard_behaviour_determines_no_content_GET(self):
- """Ensure view.DATA returns None for GET request with no content."""
- self.ensure_determines_no_content_GET(RequestMixin())
-
- def test_standard_behaviour_determines_no_content_HEAD(self):
- """Ensure view.DATA returns None for HEAD request."""
- self.ensure_determines_no_content_HEAD(RequestMixin())
-
- def test_standard_behaviour_determines_form_content_POST(self):
- """Ensure view.DATA returns content for POST request with form content."""
- self.ensure_determines_form_content_POST(RequestMixin())
-
- def test_standard_behaviour_determines_non_form_content_POST(self):
- """Ensure view.DATA returns content for POST request with non-form content."""
- self.ensure_determines_non_form_content_POST(RequestMixin())
-
- # def test_standard_behaviour_determines_form_content_PUT(self):
- # """Ensure view.DATA returns content for PUT request with form content."""
- # self.ensure_determines_form_content_PUT(RequestMixin())
-
- def test_standard_behaviour_determines_non_form_content_PUT(self):
- """Ensure view.DATA returns content for PUT request with non-form content."""
- self.ensure_determines_non_form_content_PUT(RequestMixin())
-
- def test_overloaded_behaviour_allows_content_tunnelling(self):
- """Ensure request.DATA returns content for overloaded POST request"""
- content = 'qwerty'
- content_type = 'text/plain'
- view = RequestMixin()
- form_data = {view._CONTENT_PARAM: content,
- view._CONTENTTYPE_PARAM: content_type}
- view.request = self.req.post('/', form_data)
- view.parsers = (PlainTextParser,)
- self.assertEqual(view.DATA, content)
-
- def test_accessing_post_after_data_form(self):
- """Ensures request.POST can be accessed after request.DATA in form request"""
- form_data = {'qwerty': 'uiop'}
- view = RequestMixin()
- view.parsers = (FormParser, MultiPartParser)
- view.request = self.req.post('/', data=form_data)
-
- self.assertEqual(view.DATA.items(), form_data.items())
- self.assertEqual(view.request.POST.items(), form_data.items())
-
- @unittest.skip('This test was disabled some time ago for some reason')
- def test_accessing_post_after_data_for_json(self):
- """Ensures request.POST can be accessed after request.DATA in json request"""
- from django.utils import simplejson as json
-
- data = {'qwerty': 'uiop'}
- content = json.dumps(data)
- content_type = 'application/json'
-
- view = RequestMixin()
- view.parsers = (JSONParser,)
-
- view.request = self.req.post('/', content, content_type=content_type)
-
- self.assertEqual(view.DATA.items(), data.items())
- self.assertEqual(view.request.POST.items(), [])
-
- def test_accessing_post_after_data_for_overloaded_json(self):
- """Ensures request.POST can be accessed after request.DATA in overloaded json request"""
- from django.utils import simplejson as json
-
- data = {'qwerty': 'uiop'}
- content = json.dumps(data)
- content_type = 'application/json'
-
- view = RequestMixin()
- view.parsers = (JSONParser,)
-
- form_data = {view._CONTENT_PARAM: content,
- view._CONTENTTYPE_PARAM: content_type}
-
- view.request = self.req.post('/', data=form_data)
-
- self.assertEqual(view.DATA.items(), data.items())
- self.assertEqual(view.request.POST.items(), form_data.items())
-
- def test_accessing_data_after_post_form(self):
- """Ensures request.DATA can be accessed after request.POST in form request"""
- form_data = {'qwerty': 'uiop'}
- view = RequestMixin()
- view.parsers = (FormParser, MultiPartParser)
- view.request = self.req.post('/', data=form_data)
-
- self.assertEqual(view.request.POST.items(), form_data.items())
- self.assertEqual(view.DATA.items(), form_data.items())
-
- def test_accessing_data_after_post_for_json(self):
- """Ensures request.DATA can be accessed after request.POST in json request"""
- from django.utils import simplejson as json
-
- data = {'qwerty': 'uiop'}
- content = json.dumps(data)
- content_type = 'application/json'
-
- view = RequestMixin()
- view.parsers = (JSONParser,)
-
- view.request = self.req.post('/', content, content_type=content_type)
-
- post_items = view.request.POST.items()
-
- self.assertEqual(len(post_items), 1)
- self.assertEqual(len(post_items[0]), 2)
- self.assertEqual(post_items[0][0], content)
- self.assertEqual(view.DATA.items(), data.items())
-
- def test_accessing_data_after_post_for_overloaded_json(self):
- """Ensures request.DATA can be accessed after request.POST in overloaded json request"""
- from django.utils import simplejson as json
-
- data = {'qwerty': 'uiop'}
- content = json.dumps(data)
- content_type = 'application/json'
-
- view = RequestMixin()
- view.parsers = (JSONParser,)
-
- form_data = {view._CONTENT_PARAM: content,
- view._CONTENTTYPE_PARAM: content_type}
-
- view.request = self.req.post('/', data=form_data)
-
- self.assertEqual(view.request.POST.items(), form_data.items())
- self.assertEqual(view.DATA.items(), data.items())
-
-class TestContentParsingWithAuthentication(TestCase):
- urls = 'djangorestframework.tests.content'
-
- def setUp(self):
- self.csrf_client = Client(enforce_csrf_checks=True)
- self.username = 'john'
- self.email = 'lennon@thebeatles.com'
- self.password = 'password'
- self.user = User.objects.create_user(self.username, self.email, self.password)
- self.req = RequestFactory()
-
- def test_user_logged_in_authentication_has_post_when_not_logged_in(self):
- """Ensures request.POST exists after UserLoggedInAuthentication when user doesn't log in"""
- content = {'example': 'example'}
-
- response = self.client.post('/', content)
- self.assertEqual(status.HTTP_200_OK, response.status_code, "POST data is malformed")
-
- response = self.csrf_client.post('/', content)
- self.assertEqual(status.HTTP_200_OK, response.status_code, "POST data is malformed")
-
- # def test_user_logged_in_authentication_has_post_when_logged_in(self):
- # """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
- # self.client.login(username='john', password='password')
- # self.csrf_client.login(username='john', password='password')
- # content = {'example': 'example'}
-
- # response = self.client.post('/', content)
- # self.assertEqual(status.OK, response.status_code, "POST data is malformed")
-
- # response = self.csrf_client.post('/', content)
- # self.assertEqual(status.OK, response.status_code, "POST data is malformed")
diff --git a/djangorestframework/tests/files.py b/djangorestframework/tests/files.py
deleted file mode 100644
index d3b1cc56..00000000
--- a/djangorestframework/tests/files.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from django.test import TestCase
-from django import forms
-from djangorestframework.compat import RequestFactory
-from djangorestframework.views import View
-from djangorestframework.resources import FormResource
-import StringIO
-
-class UploadFilesTests(TestCase):
- """Check uploading of files"""
- def setUp(self):
- self.factory = RequestFactory()
-
- def test_upload_file(self):
-
- class FileForm(forms.Form):
- file = forms.FileField()
-
- class MockView(View):
- permissions = ()
- form = FileForm
-
- def post(self, request, *args, **kwargs):
- return {'FILE_NAME': self.CONTENT['file'].name,
- 'FILE_CONTENT': self.CONTENT['file'].read()}
-
- file = StringIO.StringIO('stuff')
- file.name = 'stuff.txt'
- request = self.factory.post('/', {'file': file})
- view = MockView.as_view()
- response = view(request)
- self.assertEquals(response.content, '{"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"}')
-
diff --git a/djangorestframework/tests/methods.py b/djangorestframework/tests/methods.py
deleted file mode 100644
index 4b90a21f..00000000
--- a/djangorestframework/tests/methods.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from django.test import TestCase
-from djangorestframework.compat import RequestFactory
-from djangorestframework.mixins import RequestMixin
-
-
-class TestMethodOverloading(TestCase):
- def setUp(self):
- self.req = RequestFactory()
-
- def test_standard_behaviour_determines_GET(self):
- """GET requests identified"""
- view = RequestMixin()
- view.request = self.req.get('/')
- self.assertEqual(view.method, 'GET')
-
- def test_standard_behaviour_determines_POST(self):
- """POST requests identified"""
- view = RequestMixin()
- view.request = self.req.post('/')
- self.assertEqual(view.method, 'POST')
-
- def test_overloaded_POST_behaviour_determines_overloaded_method(self):
- """POST requests can be overloaded to another method by setting a reserved form field"""
- view = RequestMixin()
- view.request = self.req.post('/', {view._METHOD_PARAM: 'DELETE'})
- self.assertEqual(view.method, 'DELETE')
-
- def test_HEAD_is_a_valid_method(self):
- """HEAD requests identified"""
- view = RequestMixin()
- view.request = self.req.head('/')
- self.assertEqual(view.method, 'HEAD')
diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py
deleted file mode 100644
index 8268fdca..00000000
--- a/djangorestframework/tests/mixins.py
+++ /dev/null
@@ -1,291 +0,0 @@
-"""Tests for the mixin module"""
-from django.test import TestCase
-from django.utils import simplejson as json
-from djangorestframework import status
-from djangorestframework.compat import RequestFactory
-from django.contrib.auth.models import Group, User
-from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
-from djangorestframework.resources import ModelResource
-from djangorestframework.response import Response, ErrorResponse
-from djangorestframework.tests.models import CustomUser
-from djangorestframework.tests.testcases import TestModelsTestCase
-from djangorestframework.views import View
-
-
-class TestModelRead(TestModelsTestCase):
- """Tests on ReadModelMixin"""
-
- def setUp(self):
- super(TestModelRead, self).setUp()
- self.req = RequestFactory()
-
- def test_read(self):
- Group.objects.create(name='other group')
- group = Group.objects.create(name='my group')
-
- class GroupResource(ModelResource):
- model = Group
-
- request = self.req.get('/groups')
- mixin = ReadModelMixin()
- mixin.resource = GroupResource
-
- response = mixin.get(request, id=group.id)
- self.assertEquals(group.name, response.name)
-
- def test_read_404(self):
- class GroupResource(ModelResource):
- model = Group
-
- request = self.req.get('/groups')
- mixin = ReadModelMixin()
- mixin.resource = GroupResource
-
- self.assertRaises(ErrorResponse, mixin.get, request, id=12345)
-
-
-class TestModelCreation(TestModelsTestCase):
- """Tests on CreateModelMixin"""
-
- def setUp(self):
- super(TestModelsTestCase, self).setUp()
- self.req = RequestFactory()
-
- def test_creation(self):
- self.assertEquals(0, Group.objects.count())
-
- class GroupResource(ModelResource):
- model = Group
-
- form_data = {'name': 'foo'}
- request = self.req.post('/groups', data=form_data)
- mixin = CreateModelMixin()
- mixin.resource = GroupResource
- mixin.CONTENT = form_data
-
- response = mixin.post(request)
- self.assertEquals(1, Group.objects.count())
- self.assertEquals('foo', response.cleaned_content.name)
-
- def test_creation_with_m2m_relation(self):
- class UserResource(ModelResource):
- model = User
-
- def url(self, instance):
- return "/users/%i" % instance.id
-
- group = Group(name='foo')
- group.save()
-
- form_data = {
- 'username': 'bar',
- 'password': 'baz',
- 'groups': [group.id]
- }
- request = self.req.post('/groups', data=form_data)
- cleaned_data = dict(form_data)
- cleaned_data['groups'] = [group]
- mixin = CreateModelMixin()
- mixin.resource = UserResource
- mixin.CONTENT = cleaned_data
-
- response = mixin.post(request)
- self.assertEquals(1, User.objects.count())
- self.assertEquals(1, response.cleaned_content.groups.count())
- self.assertEquals('foo', response.cleaned_content.groups.all()[0].name)
-
- def test_creation_with_m2m_relation_through(self):
- """
- Tests creation where the m2m relation uses a through table
- """
- class UserResource(ModelResource):
- model = CustomUser
-
- def url(self, instance):
- return "/customusers/%i" % instance.id
-
- form_data = {'username': 'bar0', 'groups': []}
- request = self.req.post('/groups', data=form_data)
- cleaned_data = dict(form_data)
- cleaned_data['groups'] = []
- mixin = CreateModelMixin()
- mixin.resource = UserResource
- mixin.CONTENT = cleaned_data
-
- response = mixin.post(request)
- self.assertEquals(1, CustomUser.objects.count())
- self.assertEquals(0, response.cleaned_content.groups.count())
-
- group = Group(name='foo1')
- group.save()
-
- form_data = {'username': 'bar1', 'groups': [group.id]}
- request = self.req.post('/groups', data=form_data)
- cleaned_data = dict(form_data)
- cleaned_data['groups'] = [group]
- mixin = CreateModelMixin()
- mixin.resource = UserResource
- mixin.CONTENT = cleaned_data
-
- response = mixin.post(request)
- self.assertEquals(2, CustomUser.objects.count())
- self.assertEquals(1, response.cleaned_content.groups.count())
- self.assertEquals('foo1', response.cleaned_content.groups.all()[0].name)
-
- group2 = Group(name='foo2')
- group2.save()
-
- form_data = {'username': 'bar2', 'groups': [group.id, group2.id]}
- request = self.req.post('/groups', data=form_data)
- cleaned_data = dict(form_data)
- cleaned_data['groups'] = [group, group2]
- mixin = CreateModelMixin()
- mixin.resource = UserResource
- mixin.CONTENT = cleaned_data
-
- response = mixin.post(request)
- self.assertEquals(3, CustomUser.objects.count())
- self.assertEquals(2, response.cleaned_content.groups.count())
- self.assertEquals('foo1', response.cleaned_content.groups.all()[0].name)
- self.assertEquals('foo2', response.cleaned_content.groups.all()[1].name)
-
-
-class MockPaginatorView(PaginatorMixin, View):
- total = 60
-
- def get(self, request):
- return range(0, self.total)
-
- def post(self, request):
- return Response(status.HTTP_201_CREATED, {'status': 'OK'})
-
-
-class TestPagination(TestCase):
- def setUp(self):
- self.req = RequestFactory()
-
- def test_default_limit(self):
- """ Tests if pagination works without overwriting the limit """
- request = self.req.get('/paginator')
- response = MockPaginatorView.as_view()(request)
-
- content = json.loads(response.content)
-
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(MockPaginatorView.total, content['total'])
- self.assertEqual(MockPaginatorView.limit, content['per_page'])
-
- self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
-
- def test_overwriting_limit(self):
- """ Tests if the limit can be overwritten """
- limit = 10
-
- request = self.req.get('/paginator')
- response = MockPaginatorView.as_view(limit=limit)(request)
-
- content = json.loads(response.content)
-
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(content['per_page'], limit)
-
- self.assertEqual(range(0, limit), content['results'])
-
- def test_limit_param(self):
- """ Tests if the client can set the limit """
- from math import ceil
-
- limit = 5
- num_pages = int(ceil(MockPaginatorView.total / float(limit)))
-
- request = self.req.get('/paginator/?limit=%d' % limit)
- response = MockPaginatorView.as_view()(request)
-
- content = json.loads(response.content)
-
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(MockPaginatorView.total, content['total'])
- self.assertEqual(limit, content['per_page'])
- self.assertEqual(num_pages, content['pages'])
-
- def test_exceeding_limit(self):
- """ Makes sure the client cannot exceed the default limit """
- from math import ceil
-
- limit = MockPaginatorView.limit + 10
- num_pages = int(ceil(MockPaginatorView.total / float(limit)))
-
- request = self.req.get('/paginator/?limit=%d' % limit)
- response = MockPaginatorView.as_view()(request)
-
- content = json.loads(response.content)
-
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(MockPaginatorView.total, content['total'])
- self.assertNotEqual(limit, content['per_page'])
- self.assertNotEqual(num_pages, content['pages'])
- self.assertEqual(MockPaginatorView.limit, content['per_page'])
-
- def test_only_works_for_get(self):
- """ Pagination should only work for GET requests """
- request = self.req.post('/paginator', data={'content': 'spam'})
- response = MockPaginatorView.as_view()(request)
-
- content = json.loads(response.content)
-
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
- self.assertEqual(None, content.get('per_page'))
- self.assertEqual('OK', content['status'])
-
- def test_non_int_page(self):
- """ Tests that it can handle invalid values """
- request = self.req.get('/paginator/?page=spam')
- response = MockPaginatorView.as_view()(request)
-
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_page_range(self):
- """ Tests that the page range is handle correctly """
- request = self.req.get('/paginator/?page=0')
- response = MockPaginatorView.as_view()(request)
- content = json.loads(response.content)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- request = self.req.get('/paginator/')
- response = MockPaginatorView.as_view()(request)
- content = json.loads(response.content)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
-
- num_pages = content['pages']
-
- request = self.req.get('/paginator/?page=%d' % num_pages)
- response = MockPaginatorView.as_view()(request)
- content = json.loads(response.content)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertEqual(range(MockPaginatorView.limit*(num_pages-1), MockPaginatorView.total), content['results'])
-
- request = self.req.get('/paginator/?page=%d' % (num_pages + 1,))
- response = MockPaginatorView.as_view()(request)
- content = json.loads(response.content)
- self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
-
- def test_existing_query_parameters_are_preserved(self):
- """ Tests that existing query parameters are preserved when
- generating next/previous page links """
- request = self.req.get('/paginator/?foo=bar&another=something')
- response = MockPaginatorView.as_view()(request)
- content = json.loads(response.content)
- self.assertEqual(response.status_code, status.HTTP_200_OK)
- self.assertTrue('foo=bar' in content['next'])
- self.assertTrue('another=something' in content['next'])
- self.assertTrue('page=2' in content['next'])
-
- def test_duplicate_parameters_are_not_created(self):
- """ Regression: ensure duplicate "page" parameters are not added to
- paginated URLs. So page 1 should contain ?page=2, not ?page=1&page=2 """
- request = self.req.get('/paginator/?page=1')
- response = MockPaginatorView.as_view()(request)
- content = json.loads(response.content)
- self.assertTrue('page=2' in content['next'])
- self.assertFalse('page=1' in content['next'])
diff --git a/djangorestframework/tests/models.py b/djangorestframework/tests/models.py
deleted file mode 100644
index 4cae68b6..00000000
--- a/djangorestframework/tests/models.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from django.db import models
-from django.contrib.auth.models import Group
-
-class CustomUser(models.Model):
- """
- A custom user model, which uses a 'through' table for the foreign key
- """
- username = models.CharField(max_length=255, unique=True)
- groups = models.ManyToManyField(
- to=Group, blank=True, null=True, through='UserGroupMap'
- )
-
- @models.permalink
- def get_absolute_url(self):
- return ('custom_user', (), {
- 'pk': self.id
- })
-
-
-class UserGroupMap(models.Model):
- user = models.ForeignKey(to=CustomUser)
- group = models.ForeignKey(to=Group)
-
- @models.permalink
- def get_absolute_url(self):
- return ('user_group_map', (), {
- 'pk': self.id
- })
diff --git a/djangorestframework/tests/modelviews.py b/djangorestframework/tests/modelviews.py
deleted file mode 100644
index ccd8513f..00000000
--- a/djangorestframework/tests/modelviews.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from django.forms import ModelForm
-from django.contrib.auth.models import Group, User
-from djangorestframework.resources import ModelResource
-from djangorestframework.views import ListOrCreateModelView, InstanceModelView
-from djangorestframework.tests.models import CustomUser
-from djangorestframework.tests.testcases import TestModelsTestCase
-
-
-class GroupResource(ModelResource):
- model = Group
-
-
-class UserForm(ModelForm):
- class Meta:
- model = User
- exclude = ('last_login', 'date_joined')
-
-
-class UserResource(ModelResource):
- model = User
- form = UserForm
-
-
-class CustomUserResource(ModelResource):
- model = CustomUser
-
-urlpatterns = patterns('',
- url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
- url(r'^users/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)),
- url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'),
- url(r'^customusers/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)),
- url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'),
- url(r'^groups/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)),
-)
-
-
-class ModelViewTests(TestModelsTestCase):
- """Test the model views djangorestframework provides"""
- urls = 'djangorestframework.tests.modelviews'
-
- def test_creation(self):
- """Ensure that a model object can be created"""
- self.assertEqual(0, Group.objects.count())
-
- response = self.client.post('/groups/', {'name': 'foo'})
-
- self.assertEqual(response.status_code, 201)
- self.assertEqual(1, Group.objects.count())
- self.assertEqual('foo', Group.objects.all()[0].name)
-
- def test_creation_with_m2m_relation(self):
- """Ensure that a model object with a m2m relation can be created"""
- group = Group(name='foo')
- group.save()
- self.assertEqual(0, User.objects.count())
-
- response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
-
- self.assertEqual(response.status_code, 201)
- self.assertEqual(1, User.objects.count())
-
- user = User.objects.all()[0]
- self.assertEqual('bar', user.username)
- self.assertEqual('baz', user.password)
- self.assertEqual(1, user.groups.count())
-
- group = user.groups.all()[0]
- self.assertEqual('foo', group.name)
-
- def test_creation_with_m2m_relation_through(self):
- """
- Ensure that a model object with a m2m relation can be created where that
- relation uses a through table
- """
- group = Group(name='foo')
- group.save()
- self.assertEqual(0, User.objects.count())
-
- response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
-
- self.assertEqual(response.status_code, 201)
- self.assertEqual(1, CustomUser.objects.count())
-
- user = CustomUser.objects.all()[0]
- self.assertEqual('bar', user.username)
- self.assertEqual(1, user.groups.count())
-
- group = user.groups.all()[0]
- self.assertEqual('foo', group.name)
diff --git a/djangorestframework/tests/oauthentication.py b/djangorestframework/tests/oauthentication.py
deleted file mode 100644
index 29f2c44e..00000000
--- a/djangorestframework/tests/oauthentication.py
+++ /dev/null
@@ -1,212 +0,0 @@
-import time
-
-from django.conf.urls.defaults import patterns, url, include
-from django.contrib.auth.models import User
-from django.test import Client, TestCase
-
-from djangorestframework.views import View
-
-# Since oauth2 / django-oauth-plus are optional dependancies, we don't want to
-# always run these tests.
-
-# Unfortunatly we can't skip tests easily until 2.7, se we'll just do this for now.
-try:
- import oauth2 as oauth
- from oauth_provider.decorators import oauth_required
- from oauth_provider.models import Resource, Consumer, Token
-
-except ImportError:
- pass
-
-else:
- # Alrighty, we're good to go here.
- class ClientView(View):
- def get(self, request):
- return {'resource': 'Protected!'}
-
- urlpatterns = patterns('',
- url(r'^$', oauth_required(ClientView.as_view())),
- url(r'^oauth/', include('oauth_provider.urls')),
- url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
- )
-
-
- class OAuthTests(TestCase):
- """
- OAuth authentication:
- * the user would like to access his API data from a third-party website
- * the third-party website proposes a link to get that API data
- * the user is redirected to the API and must log in if not authenticated
- * the API displays a webpage to confirm that the user trusts the third-party website
- * if confirmed, the user is redirected to the third-party website through the callback view
- * the third-party website is able to retrieve data from the API
- """
- urls = 'djangorestframework.tests.oauthentication'
-
- def setUp(self):
- self.client = Client()
- self.username = 'john'
- self.email = 'lennon@thebeatles.com'
- self.password = 'password'
- self.user = User.objects.create_user(self.username, self.email, self.password)
-
- # OAuth requirements
- self.resource = Resource(name='data', url='/')
- self.resource.save()
- self.CONSUMER_KEY = 'dpf43f3p2l4k3l03'
- self.CONSUMER_SECRET = 'kd94hf93k423kf44'
- self.consumer = Consumer(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET,
- name='api.example.com', user=self.user)
- self.consumer.save()
-
- def test_oauth_invalid_and_anonymous_access(self):
- """
- Verify that the resource is protected and the OAuth authorization view
- require the user to be logged in.
- """
- response = self.client.get('/')
- self.assertEqual(response.content, 'Invalid request parameters.')
- self.assertEqual(response.status_code, 401)
- response = self.client.get('/oauth/authorize/', follow=True)
- self.assertRedirects(response, '/accounts/login/?next=/oauth/authorize/')
-
- def test_oauth_authorize_access(self):
- """
- Verify that once logged in, the user can access the authorization page
- but can't display the page because the request token is not specified.
- """
- self.client.login(username=self.username, password=self.password)
- response = self.client.get('/oauth/authorize/', follow=True)
- self.assertEqual(response.content, 'No request token specified.')
-
- def _create_request_token_parameters(self):
- """
- A shortcut to create request's token parameters.
- """
- return {
- 'oauth_consumer_key': self.CONSUMER_KEY,
- 'oauth_signature_method': 'PLAINTEXT',
- 'oauth_signature': '%s&' % self.CONSUMER_SECRET,
- 'oauth_timestamp': str(int(time.time())),
- 'oauth_nonce': 'requestnonce',
- 'oauth_version': '1.0',
- 'oauth_callback': 'http://api.example.com/request_token_ready',
- 'scope': 'data',
- }
-
- def test_oauth_request_token_retrieval(self):
- """
- Verify that the request token can be retrieved by the server.
- """
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- self.assertEqual(response.status_code, 200)
- token = list(Token.objects.all())[-1]
- self.failIf(token.key not in response.content)
- self.failIf(token.secret not in response.content)
-
- def test_oauth_user_request_authorization(self):
- """
- Verify that the user can access the authorization page once logged in
- and the request token has been retrieved.
- """
- # Setup
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- token = list(Token.objects.all())[-1]
-
- # Starting the test here
- self.client.login(username=self.username, password=self.password)
- parameters = {'oauth_token': token.key,}
- response = self.client.get("/oauth/authorize/", parameters)
- self.assertEqual(response.status_code, 200)
- self.failIf(not response.content.startswith('Fake authorize view for api.example.com with params: oauth_token='))
- self.assertEqual(token.is_approved, 0)
- parameters['authorize_access'] = 1 # fake authorization by the user
- response = self.client.post("/oauth/authorize/", parameters)
- self.assertEqual(response.status_code, 302)
- self.failIf(not response['Location'].startswith('http://api.example.com/request_token_ready?oauth_verifier='))
- token = Token.objects.get(key=token.key)
- self.failIf(token.key not in response['Location'])
- self.assertEqual(token.is_approved, 1)
-
- def _create_access_token_parameters(self, token):
- """
- A shortcut to create access' token parameters.
- """
- return {
- 'oauth_consumer_key': self.CONSUMER_KEY,
- 'oauth_token': token.key,
- 'oauth_signature_method': 'PLAINTEXT',
- 'oauth_signature': '%s&%s' % (self.CONSUMER_SECRET, token.secret),
- 'oauth_timestamp': str(int(time.time())),
- 'oauth_nonce': 'accessnonce',
- 'oauth_version': '1.0',
- 'oauth_verifier': token.verifier,
- 'scope': 'data',
- }
-
- def test_oauth_access_token_retrieval(self):
- """
- Verify that the request token can be retrieved by the server.
- """
- # Setup
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- token = list(Token.objects.all())[-1]
- self.client.login(username=self.username, password=self.password)
- parameters = {'oauth_token': token.key,}
- response = self.client.get("/oauth/authorize/", parameters)
- parameters['authorize_access'] = 1 # fake authorization by the user
- response = self.client.post("/oauth/authorize/", parameters)
- token = Token.objects.get(key=token.key)
-
- # Starting the test here
- response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
- self.assertEqual(response.status_code, 200)
- self.failIf(not response.content.startswith('oauth_token_secret='))
- access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
- self.failIf(access_token.key not in response.content)
- self.failIf(access_token.secret not in response.content)
- self.assertEqual(access_token.user.username, 'john')
-
- def _create_access_parameters(self, access_token):
- """
- A shortcut to create access' parameters.
- """
- parameters = {
- 'oauth_consumer_key': self.CONSUMER_KEY,
- 'oauth_token': access_token.key,
- 'oauth_signature_method': 'HMAC-SHA1',
- 'oauth_timestamp': str(int(time.time())),
- 'oauth_nonce': 'accessresourcenonce',
- 'oauth_version': '1.0',
- }
- oauth_request = oauth.Request.from_token_and_callback(access_token,
- http_url='http://testserver/', parameters=parameters)
- signature_method = oauth.SignatureMethod_HMAC_SHA1()
- signature = signature_method.sign(oauth_request, self.consumer, access_token)
- parameters['oauth_signature'] = signature
- return parameters
-
- def test_oauth_protected_resource_access(self):
- """
- Verify that the request token can be retrieved by the server.
- """
- # Setup
- response = self.client.get("/oauth/request_token/",
- self._create_request_token_parameters())
- token = list(Token.objects.all())[-1]
- self.client.login(username=self.username, password=self.password)
- parameters = {'oauth_token': token.key,}
- response = self.client.get("/oauth/authorize/", parameters)
- parameters['authorize_access'] = 1 # fake authorization by the user
- response = self.client.post("/oauth/authorize/", parameters)
- token = Token.objects.get(key=token.key)
- response = self.client.get("/oauth/access_token/", self._create_access_token_parameters(token))
- access_token = list(Token.objects.filter(token_type=Token.ACCESS))[-1]
-
- # Starting the test here
- response = self.client.get("/", self._create_access_token_parameters(access_token))
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, '{"resource": "Protected!"}')
diff --git a/djangorestframework/tests/package.py b/djangorestframework/tests/package.py
deleted file mode 100644
index d18128f7..00000000
--- a/djangorestframework/tests/package.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""Tests for the djangorestframework package setup."""
-from django.test import TestCase
-import djangorestframework
-
-class TestVersion(TestCase):
- """Simple sanity test to check the VERSION exists"""
-
- def test_version(self):
- """Ensure the VERSION exists."""
- djangorestframework.VERSION
-
diff --git a/djangorestframework/tests/response.py b/djangorestframework/tests/response.py
deleted file mode 100644
index d973deb4..00000000
--- a/djangorestframework/tests/response.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Right now we expect this test to fail - I'm just going to leave it commented out.
-# Looking forward to actually being able to raise ExpectedFailure sometime!
-#
-#from django.test import TestCase
-#from djangorestframework.response import Response
-#
-#
-#class TestResponse(TestCase):
-#
-# # Interface tests
-#
-# # This is mainly to remind myself that the Response interface needs to change slightly
-# def test_response_interface(self):
-# """Ensure the Response interface is as expected."""
-# response = Response()
-# getattr(response, 'status')
-# getattr(response, 'content')
-# getattr(response, 'headers')
-
diff --git a/djangorestframework/tests/reverse.py b/djangorestframework/tests/reverse.py
deleted file mode 100644
index 01ba19b9..00000000
--- a/djangorestframework/tests/reverse.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from django.test import TestCase
-from django.utils import simplejson as json
-
-from djangorestframework.renderers import JSONRenderer
-from djangorestframework.reverse import reverse, reverse_lazy
-from djangorestframework.views import View
-
-
-class ReverseView(View):
- """
- Mock resource which simply returns a URL, so that we can ensure
- that reversed URLs are fully qualified.
- """
- renderers = (JSONRenderer, )
-
- def get(self, request):
- return reverse('reverse', request=request)
-
-
-class LazyView(View):
- """
- Mock resource which simply returns a URL, so that we can ensure
- that reversed URLs are fully qualified.
- """
- renderers = (JSONRenderer, )
-
- def get(self, request):
- return reverse_lazy('lazy', request=request)
-
-
-urlpatterns = patterns('',
- url(r'^reverse$', ReverseView.as_view(), name='reverse'),
- url(r'^lazy$', LazyView.as_view(), name='lazy'),
-)
-
-
-class ReverseTests(TestCase):
- """
- Tests for fully qualifed URLs when using `reverse`.
- """
- urls = 'djangorestframework.tests.reverse'
-
- def test_reversed_urls_are_fully_qualified(self):
- response = self.client.get('/reverse')
- self.assertEqual(json.loads(response.content),
- 'http://testserver/reverse')
-
- #def test_reversed_lazy_urls_are_fully_qualified(self):
- # response = self.client.get('/lazy')
- # self.assertEqual(json.loads(response.content),
- # 'http://testserver/lazy')
diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py
deleted file mode 100644
index 1a4eaa10..00000000
--- a/djangorestframework/tests/serializer.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""Tests for the resource module"""
-from django.db import models
-from django.test import TestCase
-from django.utils.translation import ugettext_lazy
-from djangorestframework.serializer import Serializer
-
-import datetime
-import decimal
-
-class TestObjectToData(TestCase):
- """
- Tests for the Serializer class.
- """
-
- def setUp(self):
- self.serializer = Serializer()
- self.serialize = self.serializer.serialize
-
- def test_decimal(self):
- """Decimals need to be converted to a string representation."""
- self.assertEquals(self.serialize(decimal.Decimal('1.5')), decimal.Decimal('1.5'))
-
- def test_function(self):
- """Functions with no arguments should be called."""
- def foo():
- return 1
- self.assertEquals(self.serialize(foo), 1)
-
- def test_method(self):
- """Methods with only a ``self`` argument should be called."""
- class Foo(object):
- def foo(self):
- return 1
- self.assertEquals(self.serialize(Foo().foo), 1)
-
- def test_datetime(self):
- """datetime objects are left as-is."""
- now = datetime.datetime.now()
- self.assertEquals(self.serialize(now), now)
-
- def test_dict_method_name_collision(self):
- """dict with key that collides with dict method name"""
- self.assertEquals(self.serialize({'items': 'foo'}), {'items': u'foo'})
- self.assertEquals(self.serialize({'keys': 'foo'}), {'keys': u'foo'})
- self.assertEquals(self.serialize({'values': 'foo'}), {'values': u'foo'})
-
- def test_ugettext_lazy(self):
- self.assertEquals(self.serialize(ugettext_lazy('foobar')), u'foobar')
-
-
-class TestFieldNesting(TestCase):
- """
- Test nesting the fields in the Serializer class
- """
- def setUp(self):
- self.serializer = Serializer()
- self.serialize = self.serializer.serialize
-
- class M1(models.Model):
- field1 = models.CharField(max_length=256)
- field2 = models.CharField(max_length=256)
-
- class M2(models.Model):
- field = models.OneToOneField(M1)
-
- class M3(models.Model):
- field = models.ForeignKey(M1)
-
- self.m1 = M1(field1='foo', field2='bar')
- self.m2 = M2(field=self.m1)
- self.m3 = M3(field=self.m1)
-
-
- def test_tuple_nesting(self):
- """
- Test tuple nesting on `fields` attr
- """
- class SerializerM2(Serializer):
- fields = (('field', ('field1',)),)
-
- class SerializerM3(Serializer):
- fields = (('field', ('field2',)),)
-
- self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
- self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
-
-
- def test_serializer_class_nesting(self):
- """
- Test related model serialization
- """
- class NestedM2(Serializer):
- fields = ('field1', )
-
- class NestedM3(Serializer):
- fields = ('field2', )
-
- class SerializerM2(Serializer):
- fields = [('field', NestedM2)]
-
- class SerializerM3(Serializer):
- fields = [('field', NestedM3)]
-
- self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
- self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
-
- # def test_serializer_no_fields(self):
- # """
- # Test related serializer works when the fields attr isn't present. Fix for
- # #178.
- # """
- # class NestedM2(Serializer):
- # fields = ('field1', )
-
- # class NestedM3(Serializer):
- # fields = ('field2', )
-
- # class SerializerM2(Serializer):
- # include = [('field', NestedM2)]
- # exclude = ('id', )
-
- # class SerializerM3(Serializer):
- # fields = [('field', NestedM3)]
-
- # self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
- # self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
-
- def test_serializer_classname_nesting(self):
- """
- Test related model serialization
- """
- class SerializerM2(Serializer):
- fields = [('field', 'NestedM2')]
-
- class SerializerM3(Serializer):
- fields = [('field', 'NestedM3')]
-
- class NestedM2(Serializer):
- fields = ('field1', )
-
- class NestedM3(Serializer):
- fields = ('field2', )
-
- self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
- self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
-
- def test_serializer_overridden_hook_method(self):
- """
- Test serializing a model instance which overrides a class method on the
- serializer. Checks for correct behaviour in odd edge case.
- """
- class SerializerM2(Serializer):
- fields = ('overridden', )
-
- def overridden(self):
- return False
-
- self.m2.overridden = True
- self.assertEqual(SerializerM2().serialize_model(self.m2),
- {'overridden': True})
diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py
deleted file mode 100644
index 15d92231..00000000
--- a/djangorestframework/tests/validators.py
+++ /dev/null
@@ -1,325 +0,0 @@
-from django import forms
-from django.db import models
-from django.test import TestCase
-from djangorestframework.resources import FormResource, ModelResource
-from djangorestframework.response import ErrorResponse
-from djangorestframework.views import View
-
-
-class TestDisabledValidations(TestCase):
- """Tests on FormValidator with validation disabled by setting form to None"""
-
- def test_disabled_form_validator_returns_content_unchanged(self):
- """If the view's form attribute is None then FormValidator(view).validate_request(content, None)
- should just return the content unmodified."""
- class DisabledFormResource(FormResource):
- form = None
-
- class MockView(View):
- resource = DisabledFormResource
-
- view = MockView()
- content = {'qwerty': 'uiop'}
- self.assertEqual(FormResource(view).validate_request(content, None), content)
-
- def test_disabled_form_validator_get_bound_form_returns_none(self):
- """If the view's form attribute is None on then
- FormValidator(view).get_bound_form(content) should just return None."""
- class DisabledFormResource(FormResource):
- form = None
-
- class MockView(View):
- resource = DisabledFormResource
-
- view = MockView()
- content = {'qwerty': 'uiop'}
- self.assertEqual(FormResource(view).get_bound_form(content), None)
-
- def test_disabled_model_form_validator_returns_content_unchanged(self):
- """If the view's form is None and does not have a Resource with a model set then
- ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
-
- class DisabledModelFormView(View):
- resource = ModelResource
-
- view = DisabledModelFormView()
- content = {'qwerty': 'uiop'}
- self.assertEqual(ModelResource(view).get_bound_form(content), None)
-
- def test_disabled_model_form_validator_get_bound_form_returns_none(self):
- """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
- class DisabledModelFormView(View):
- resource = ModelResource
-
- view = DisabledModelFormView()
- content = {'qwerty': 'uiop'}
- self.assertEqual(ModelResource(view).get_bound_form(content), None)
-
-
-class TestNonFieldErrors(TestCase):
- """Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
-
- def test_validate_failed_due_to_non_field_error_returns_appropriate_message(self):
- """If validation fails with a non-field error, ensure the response a non-field error"""
- class MockForm(forms.Form):
- field1 = forms.CharField(required=False)
- field2 = forms.CharField(required=False)
- ERROR_TEXT = 'You may not supply both field1 and field2'
-
- def clean(self):
- if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data:
- raise forms.ValidationError(self.ERROR_TEXT)
- return self.cleaned_data
-
- class MockResource(FormResource):
- form = MockForm
-
- class MockView(View):
- pass
-
- view = MockView()
- content = {'field1': 'example1', 'field2': 'example2'}
- try:
- MockResource(view).validate_request(content, None)
- except ErrorResponse, exc:
- self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
- else:
- self.fail('ErrorResponse was not raised')
-
-
-class TestFormValidation(TestCase):
- """Tests which check basic form validation.
- Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
- (ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)"""
- def setUp(self):
- class MockForm(forms.Form):
- qwerty = forms.CharField(required=True)
-
- class MockFormResource(FormResource):
- form = MockForm
-
- class MockModelResource(ModelResource):
- form = MockForm
-
- class MockFormView(View):
- resource = MockFormResource
-
- class MockModelFormView(View):
- resource = MockModelResource
-
- self.MockFormResource = MockFormResource
- self.MockModelResource = MockModelResource
- self.MockFormView = MockFormView
- self.MockModelFormView = MockModelFormView
-
- def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
- """If the content is already valid and clean then validate(content) should just return the content unmodified."""
- content = {'qwerty': 'uiop'}
- self.assertEqual(validator.validate_request(content, None), content)
-
- def validation_failure_raises_response_exception(self, validator):
- """If form validation fails a ResourceException 400 (Bad Request) should be raised."""
- content = {}
- self.assertRaises(ErrorResponse, validator.validate_request, content, None)
-
- def validation_does_not_allow_extra_fields_by_default(self, validator):
- """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
- It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
- broken clients more easily (eg submitting content with a misnamed field)"""
- content = {'qwerty': 'uiop', 'extra': 'extra'}
- self.assertRaises(ErrorResponse, validator.validate_request, content, None)
-
- def validation_allows_extra_fields_if_explicitly_set(self, validator):
- """If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
- content = {'qwerty': 'uiop', 'extra': 'extra'}
- validator._validate(content, None, allowed_extra_fields=('extra',))
-
- def validation_allows_unknown_fields_if_explicitly_allowed(self, validator):
- """If we set ``unknown_form_fields`` on the form resource, then don't
- raise errors on unexpected request data"""
- content = {'qwerty': 'uiop', 'extra': 'extra'}
- validator.allow_unknown_form_fields = True
- self.assertEqual({'qwerty': u'uiop'},
- validator.validate_request(content, None),
- "Resource didn't accept unknown fields.")
- validator.allow_unknown_form_fields = False
-
- def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
- """If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
- content = {'qwerty': 'uiop'}
- self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
-
- def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
- """If validation fails due to no content, ensure the response contains a single non-field error"""
- content = {}
- try:
- validator.validate_request(content, None)
- except ErrorResponse, exc:
- self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
- else:
- self.fail('ResourceException was not raised')
-
- def validation_failed_due_to_field_error_returns_appropriate_message(self, validator):
- """If validation fails due to a field error, ensure the response contains a single field error"""
- content = {'qwerty': ''}
- try:
- validator.validate_request(content, None)
- except ErrorResponse, exc:
- self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
- else:
- self.fail('ResourceException was not raised')
-
- def validation_failed_due_to_invalid_field_returns_appropriate_message(self, validator):
- """If validation fails due to an invalid field, ensure the response contains a single field error"""
- content = {'qwerty': 'uiop', 'extra': 'extra'}
- try:
- validator.validate_request(content, None)
- except ErrorResponse, exc:
- self.assertEqual(exc.response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
- else:
- self.fail('ResourceException was not raised')
-
- def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator):
- """If validation for multiple reasons, ensure the response contains each error"""
- content = {'qwerty': '', 'extra': 'extra'}
- try:
- validator.validate_request(content, None)
- except ErrorResponse, exc:
- self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
- 'extra': ['This field does not exist.']}})
- else:
- self.fail('ResourceException was not raised')
-
- # Tests on FormResource
-
- def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
-
- def test_form_validation_failure_raises_response_exception(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_failure_raises_response_exception(validator)
-
- def test_validation_does_not_allow_extra_fields_by_default(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_does_not_allow_extra_fields_by_default(validator)
-
- def test_validation_allows_extra_fields_if_explicitly_set(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_allows_extra_fields_if_explicitly_set(validator)
-
- def test_validation_allows_unknown_fields_if_explicitly_allowed(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_allows_unknown_fields_if_explicitly_allowed(validator)
-
- def test_validation_does_not_require_extra_fields_if_explicitly_set(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
-
- def test_validation_failed_due_to_no_content_returns_appropriate_message(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
-
- def test_validation_failed_due_to_field_error_returns_appropriate_message(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
-
- def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
-
- def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
- validator = self.MockFormResource(self.MockFormView())
- self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
-
- # Same tests on ModelResource
-
- def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
-
- def test_modelform_validation_failure_raises_response_exception(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_failure_raises_response_exception(validator)
-
- def test_modelform_validation_does_not_allow_extra_fields_by_default(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_does_not_allow_extra_fields_by_default(validator)
-
- def test_modelform_validation_allows_extra_fields_if_explicitly_set(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_allows_extra_fields_if_explicitly_set(validator)
-
- def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
-
- def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
-
- def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
-
- def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
-
- def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
- validator = self.MockModelResource(self.MockModelFormView())
- self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
-
-
-class TestModelFormValidator(TestCase):
- """Tests specific to ModelFormValidatorMixin"""
-
- def setUp(self):
- """Create a validator for a model with two fields and a property."""
- class MockModel(models.Model):
- qwerty = models.CharField(max_length=256)
- uiop = models.CharField(max_length=256, blank=True)
-
- @property
- def readonly(self):
- return 'read only'
-
- class MockResource(ModelResource):
- model = MockModel
-
- class MockView(View):
- resource = MockResource
-
- self.validator = MockResource(MockView)
-
- def test_property_fields_are_allowed_on_model_forms(self):
- """Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
- content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only'}
- self.assertEqual(self.validator.validate_request(content, None), content)
-
- def test_property_fields_are_not_required_on_model_forms(self):
- """Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
- content = {'qwerty': 'example', 'uiop': 'example'}
- self.assertEqual(self.validator.validate_request(content, None), content)
-
- def test_extra_fields_not_allowed_on_model_forms(self):
- """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
- It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
- broken clients more easily (eg submitting content with a misnamed field)"""
- content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only', 'extra': 'extra'}
- self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
-
- def test_validate_requires_fields_on_model_forms(self):
- """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
- It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
- broken clients more easily (eg submitting content with a misnamed field)"""
- content = {'readonly': 'read only'}
- self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
-
- def test_validate_does_not_require_blankable_fields_on_model_forms(self):
- """Test standard ModelForm validation behaviour - fields with blank=True are not required."""
- content = {'qwerty': 'example', 'readonly': 'read only'}
- self.validator.validate_request(content, None)
-
- def test_model_form_validator_uses_model_forms(self):
- self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))
diff --git a/djangorestframework/tests/views.py b/djangorestframework/tests/views.py
deleted file mode 100644
index 418b4b16..00000000
--- a/djangorestframework/tests/views.py
+++ /dev/null
@@ -1,137 +0,0 @@
-from django.core.urlresolvers import reverse
-from django.conf.urls.defaults import patterns, url, include
-from django.http import HttpResponse
-from django.test import TestCase
-from django.test import Client
-from django import forms
-from django.db import models
-
-from djangorestframework.views import View
-from djangorestframework.parsers import JSONParser
-from djangorestframework.resources import ModelResource
-from djangorestframework.views import ListOrCreateModelView, InstanceModelView
-
-from StringIO import StringIO
-
-
-class MockView(View):
- """This is a basic mock view"""
- pass
-
-
-class MockViewFinal(View):
- """View with final() override"""
-
- def final(self, request, response, *args, **kwargs):
- return HttpResponse('{"test": "passed"}', content_type="application/json")
-
-class ResourceMockView(View):
- """This is a resource-based mock view"""
-
- class MockForm(forms.Form):
- foo = forms.BooleanField(required=False)
- bar = forms.IntegerField(help_text='Must be an integer.')
- baz = forms.CharField(max_length=32)
-
- form = MockForm
-
-class MockResource(ModelResource):
- """This is a mock model-based resource"""
-
- class MockResourceModel(models.Model):
- foo = models.BooleanField()
- bar = models.IntegerField(help_text='Must be an integer.')
- baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
-
- model = MockResourceModel
- fields = ('foo', 'bar', 'baz')
-
-urlpatterns = patterns('',
- url(r'^mock/$', MockView.as_view()),
- url(r'^mock/final/$', MockViewFinal.as_view()),
- url(r'^resourcemock/$', ResourceMockView.as_view()),
- url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)),
- url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)),
- url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
-)
-
-class BaseViewTests(TestCase):
- """Test the base view class of djangorestframework"""
- urls = 'djangorestframework.tests.views'
-
- def test_view_call_final(self):
- response = self.client.options('/mock/final/')
- self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
- parser = JSONParser(None)
- (data, files) = parser.parse(StringIO(response.content))
- self.assertEqual(data['test'], 'passed')
-
- def test_options_method_simple_view(self):
- response = self.client.options('/mock/')
- self._verify_options_response(response,
- name='Mock',
- description='This is a basic mock view')
-
- def test_options_method_resource_view(self):
- response = self.client.options('/resourcemock/')
- self._verify_options_response(response,
- name='Resource Mock',
- description='This is a resource-based mock view',
- fields={'foo':'BooleanField',
- 'bar':'IntegerField',
- 'baz':'CharField',
- })
-
- def test_options_method_model_resource_list_view(self):
- response = self.client.options('/model/')
- self._verify_options_response(response,
- name='Mock List',
- description='This is a mock model-based resource',
- fields={'foo':'BooleanField',
- 'bar':'IntegerField',
- 'baz':'CharField',
- })
-
- def test_options_method_model_resource_detail_view(self):
- response = self.client.options('/model/0/')
- self._verify_options_response(response,
- name='Mock Instance',
- description='This is a mock model-based resource',
- fields={'foo':'BooleanField',
- 'bar':'IntegerField',
- 'baz':'CharField',
- })
-
- def _verify_options_response(self, response, name, description, fields=None, status=200,
- mime_type='application/json'):
- self.assertEqual(response.status_code, status)
- self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
- parser = JSONParser(None)
- (data, files) = parser.parse(StringIO(response.content))
- self.assertTrue('application/json' in data['renders'])
- self.assertEqual(name, data['name'])
- self.assertEqual(description, data['description'])
- if fields is None:
- self.assertFalse(hasattr(data, 'fields'))
- else:
- self.assertEqual(data['fields'], fields)
-
-
-class ExtraViewsTests(TestCase):
- """Test the extra views djangorestframework provides"""
- urls = 'djangorestframework.tests.views'
-
- def test_login_view(self):
- """Ensure the login view exists"""
- response = self.client.get(reverse('djangorestframework:login'))
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
-
- def test_logout_view(self):
- """Ensure the logout view exists"""
- response = self.client.get(reverse('djangorestframework:logout'))
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
-
- # TODO: Add login/logout behaviour tests
-
diff --git a/djangorestframework/urls.py b/djangorestframework/urls.py
deleted file mode 100644
index 3fa813ea..00000000
--- a/djangorestframework/urls.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-
-
-template_name = {'template_name': 'djangorestframework/login.html'}
-
-urlpatterns = patterns('django.contrib.auth.views',
- url(r'^login/$', 'login', template_name, name='login'),
- url(r'^logout/$', 'logout', template_name, name='logout'),
-)
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
deleted file mode 100644
index 4aa6ca0c..00000000
--- a/djangorestframework/views.py
+++ /dev/null
@@ -1,292 +0,0 @@
-"""
-The :mod:`views` module provides the Views you will most probably
-be subclassing in your implementation.
-
-By setting or modifying class attributes on your view, you change it's predefined behaviour.
-"""
-
-import re
-from django.http import HttpResponse
-from django.utils.html import escape
-from django.utils.safestring import mark_safe
-from django.views.decorators.csrf import csrf_exempt
-
-from djangorestframework.compat import View as DjangoView, apply_markdown
-from djangorestframework.response import Response, ErrorResponse
-from djangorestframework.mixins import *
-from djangorestframework import resources, renderers, parsers, authentication, permissions, status
-
-
-__all__ = (
- 'View',
- 'ModelView',
- 'InstanceModelView',
- 'ListModelView',
- 'ListOrCreateModelView'
-)
-
-
-def _remove_trailing_string(content, trailing):
- """
- Strip trailing component `trailing` from `content` if it exists.
- Used when generating names from view/resource classes.
- """
- if content.endswith(trailing) and content != trailing:
- return content[:-len(trailing)]
- return content
-
-
-def _remove_leading_indent(content):
- """
- Remove leading indent from a block of text.
- Used when generating descriptions from docstrings.
- """
- whitespace_counts = [len(line) - len(line.lstrip(' '))
- for line in content.splitlines()[1:] if line.lstrip()]
-
- # unindent the content if needed
- if whitespace_counts:
- whitespace_pattern = '^' + (' ' * min(whitespace_counts))
- return re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
- return content
-
-
-def _camelcase_to_spaces(content):
- """
- Translate 'CamelCaseNames' to 'Camel Case Names'.
- Used when generating names from view/resource classes.
- """
- camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
- return re.sub(camelcase_boundry, ' \\1', content).strip()
-
-
-_resource_classes = (
- None,
- resources.Resource,
- resources.FormResource,
- resources.ModelResource
-)
-
-
-class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
- """
- Handles incoming requests and maps them to REST operations.
- Performs request deserialization, response serialization, authentication and input validation.
- """
-
- resource = None
- """
- The resource to use when validating requests and filtering responses,
- or `None` to use default behaviour.
- """
-
- renderers = renderers.DEFAULT_RENDERERS
- """
- List of renderers the resource can serialize the response with, ordered by preference.
- """
-
- parsers = parsers.DEFAULT_PARSERS
- """
- List of parsers the resource can parse the request with.
- """
-
- authentication = (authentication.UserLoggedInAuthentication,
- authentication.BasicAuthentication)
- """
- List of all authenticating methods to attempt.
- """
-
- permissions = (permissions.FullAnonAccess,)
- """
- List of all permissions that must be checked.
- """
-
- @classmethod
- def as_view(cls, **initkwargs):
- """
- Override the default :meth:`as_view` to store an instance of the view
- as an attribute on the callable function. This allows us to discover
- information about the view when we do URL reverse lookups.
- """
- view = super(View, cls).as_view(**initkwargs)
- view.cls_instance = cls(**initkwargs)
- return view
-
- @property
- def allowed_methods(self):
- """
- Return the list of allowed HTTP methods, uppercased.
- """
- return [method.upper() for method in self.http_method_names if hasattr(self, method)]
-
- def get_name(self):
- """
- Return the resource or view class name for use as this view's name.
- Override to customize.
- """
- # If this view has a resource that's been overridden, then use that resource for the name
- if getattr(self, 'resource', None) not in _resource_classes:
- name = self.resource.__name__
- name = _remove_trailing_string(name, 'Resource')
- name += getattr(self, '_suffix', '')
-
- # If it's a view class with no resource then grok the name from the class name
- else:
- name = self.__class__.__name__
- name = _remove_trailing_string(name, 'View')
-
- return _camelcase_to_spaces(name)
-
- def get_description(self, html=False):
- """
- Return the resource or view docstring for use as this view's description.
- Override to customize.
- """
-
- description = None
-
- # If this view has a resource that's been overridden,
- # then try to use the resource's docstring
- if getattr(self, 'resource', None) not in _resource_classes:
- description = self.resource.__doc__
-
- # Otherwise use the view docstring
- if not description:
- description = self.__doc__ or ''
-
- description = _remove_leading_indent(description)
-
- if not isinstance(description, unicode):
- description = description.decode('UTF-8')
-
- if html:
- return self.markup_description(description)
- return description
-
- def markup_description(self, description):
- if apply_markdown:
- description = apply_markdown(description)
- else:
- description = escape(description).replace('\n', '<br />')
- return mark_safe(description)
-
- def http_method_not_allowed(self, request, *args, **kwargs):
- """
- Return an HTTP 405 error if an operation is called which does not have a handler method.
- """
- raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
- {'detail': 'Method \'%s\' not allowed on this resource.' % self.method})
-
- def initial(self, request, *args, **kargs):
- """
- Hook for any code that needs to run prior to anything else.
- Required if you want to do things like set `request.upload_handlers` before
- the authentication and dispatch handling is run.
- """
- pass
-
- def final(self, request, response, *args, **kargs):
- """
- Hook for any code that needs to run after everything else in the view.
- """
- # Always add these headers.
- response.headers['Allow'] = ', '.join(self.allowed_methods)
- # sample to allow caching using Vary http header
- response.headers['Vary'] = 'Authenticate, Accept'
-
- # merge with headers possibly set at some point in the view
- response.headers.update(self.headers)
- return self.render(response)
-
- def add_header(self, field, value):
- """
- Add *field* and *value* to the :attr:`headers` attribute of the :class:`View` class.
- """
- self.headers[field] = value
-
- # Note: session based authentication is explicitly CSRF validated,
- # all other authentication is CSRF exempt.
- @csrf_exempt
- def dispatch(self, request, *args, **kwargs):
- self.request = request
- self.args = args
- self.kwargs = kwargs
- self.headers = {}
-
- try:
- self.initial(request, *args, **kwargs)
-
- # Authenticate and check request has the relevant permissions
- self._check_permissions()
-
- # Get the appropriate handler method
- if self.method.lower() in self.http_method_names:
- handler = getattr(self, self.method.lower(), self.http_method_not_allowed)
- else:
- handler = self.http_method_not_allowed
-
- response_obj = handler(request, *args, **kwargs)
-
- # Allow return value to be either HttpResponse, Response, or an object, or None
- if isinstance(response_obj, HttpResponse):
- return response_obj
- elif isinstance(response_obj, Response):
- response = response_obj
- elif response_obj is not None:
- response = Response(status.HTTP_200_OK, response_obj)
- else:
- response = Response(status.HTTP_204_NO_CONTENT)
-
- # Pre-serialize filtering (eg filter complex objects into natively serializable types)
- response.cleaned_content = self.filter_response(response.raw_content)
-
- except ErrorResponse, exc:
- response = exc.response
-
- return self.final(request, response, *args, **kwargs)
-
- def options(self, request, *args, **kwargs):
- response_obj = {
- 'name': self.get_name(),
- 'description': self.get_description(),
- 'renders': self._rendered_media_types,
- 'parses': self._parsed_media_types,
- }
- form = self.get_bound_form()
- if form is not None:
- field_name_types = {}
- for name, field in form.fields.iteritems():
- field_name_types[name] = field.__class__.__name__
- response_obj['fields'] = field_name_types
- # Note 'ErrorResponse' is misleading, it's just any response
- # that should be rendered and returned immediately, without any
- # response filtering.
- raise ErrorResponse(status.HTTP_200_OK, response_obj)
-
-
-class ModelView(View):
- """
- A RESTful view that maps to a model in the database.
- """
- resource = resources.ModelResource
-
-
-class InstanceModelView(ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
- """
- A view which provides default operations for read/update/delete against a model instance.
- """
- _suffix = 'Instance'
-
-
-class ListModelView(ListModelMixin, ModelView):
- """
- A view which provides default operations for list, against a model in the database.
- """
- _suffix = 'List'
-
-
-class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
- """
- A view which provides default operations for list and create, against a model in the database.
- """
- _suffix = 'List'
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
new file mode 100644
index 00000000..889d16c0
--- /dev/null
+++ b/docs/api-guide/authentication.md
@@ -0,0 +1,126 @@
+<a class="github" href="authentication.py"></a>
+
+# Authentication
+
+> Auth needs to be pluggable.
+>
+> &mdash; Jacob Kaplan-Moss, ["REST worst practices"][cite]
+
+Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The [permission] and [throttling] policies can then use those credentials to determine if the request should be permitted.
+
+REST framework provides a number of authentication policies out of the box, and also allows you to implement custom policies.
+
+Authentication will run the first time either the `request.user` or `request.auth` properties are accessed, and determines how those properties are initialized.
+
+The `request.user` property will typically be set to an instance of the `contrib.auth` package's `User` class.
+
+The `request.auth` property is used for any additional authentication information, for example, it may be used to represent an authentication token that the request was signed with.
+
+## How authentication is determined
+
+The authentication policy is always defined as a list of classes. REST framework will attempt to authenticate with each class in the list, and will set `request.user` and `request.auth` using the return value of the first class that successfully authenticates.
+
+If no class authenticates, `request.user` will be set to an instance of `django.contrib.auth.models.AnonymousUser`, and `request.auth` will be set to `None`.
+
+The value of `request.user` and `request.auth` for unauthenticated requests can be modified using the `UNAUTHENTICATED_USER` and `UNAUTHENTICATED_TOKEN` settings.
+
+## Setting the authentication policy
+
+The default authentication policy may be set globally, using the `DEFAULT_AUTHENTICATION_CLASSES` setting. For example.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'rest_framework.authentication.UserBasicAuthentication',
+ 'rest_framework.authentication.SessionAuthentication',
+ )
+ }
+
+You can also set the authentication policy on a per-view basis, using the `APIView` class based views.
+
+ class ExampleView(APIView):
+ authentication_classes = (SessionAuthentication, UserBasicAuthentication)
+ permission_classes = (IsAuthenticated,)
+
+ def get(self, request, format=None):
+ content = {
+ 'user': unicode(request.user), # `django.contrib.auth.User` instance.
+ 'auth': unicode(request.auth), # None
+ }
+ return Response(content)
+
+Or, if you're using the `@api_view` decorator with function based views.
+
+ @api_view(['GET'])
+ @authentication_classes((SessionAuthentication, UserBasicAuthentication))
+ @permissions_classes((IsAuthenticated,))
+ def example_view(request, format=None):
+ content = {
+ 'user': unicode(request.user), # `django.contrib.auth.User` instance.
+ 'auth': unicode(request.auth), # None
+ }
+ return Response(content)
+
+# API Reference
+
+## BasicAuthentication
+
+This policy uses [HTTP Basic Authentication][basicauth], signed against a user's username and password. Basic authentication is generally only appropriate for testing.
+
+If successfully authenticated, `BasicAuthentication` provides the following credentials.
+
+* `request.user` will be a `django.contrib.auth.models.User` instance.
+* `request.auth` will be `None`.
+
+**Note:** If you use `BasicAuthentication` in production you must ensure that your API is only available over `https` only. You should also ensure that your API clients will always re-request the username and password at login, and will never store those details to persistent storage.
+
+## TokenAuthentication
+
+This policy uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.
+
+To use the `TokenAuthentication` policy, include `rest_framework.authtoken` in your `INSTALLED_APPS` setting.
+
+You'll also need to create tokens for your users.
+
+ from rest_framework.authtoken.models import Token
+
+ token = Token.objects.create(user=...)
+ print token.key
+
+For clients to authenticate, the token key should be included in the `Authorization` HTTP header. The key should be prefixed by the string literal "Token", with whitespace separating the two strings. For example:
+
+ Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
+
+If successfully authenticated, `TokenAuthentication` provides the following credentials.
+
+* `request.user` will be a `django.contrib.auth.models.User` instance.
+* `request.auth` will be a `rest_framework.tokenauth.models.BasicToken` instance.
+
+**Note:** If you use `TokenAuthentication` in production you must ensure that your API is only available over `https` only.
+
+## OAuthAuthentication
+
+This policy uses the [OAuth 2.0][oauth] protocol to authenticate requests. OAuth is appropriate for server-server setups, such as when you want to allow a third-party service to access your API on a user's behalf.
+
+If successfully authenticated, `OAuthAuthentication` provides the following credentials.
+
+* `request.user` will be a `django.contrib.auth.models.User` instance.
+* `request.auth` will be a `rest_framework.models.OAuthToken` instance.
+
+## SessionAuthentication
+
+This policy uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
+
+If successfully authenticated, `SessionAuthentication` provides the following credentials.
+
+* `request.user` will be a `django.contrib.auth.models.User` instance.
+* `request.auth` will be `None`.
+
+# Custom authentication
+
+To implement a custom authentication policy, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise.
+
+[cite]: http://jacobian.org/writing/rest-worst-practices/
+[basicauth]: http://tools.ietf.org/html/rfc2617
+[oauth]: http://oauth.net/2/
+[permission]: permissions.md
+[throttling]: throttling.md
diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md
new file mode 100644
index 00000000..10288c94
--- /dev/null
+++ b/docs/api-guide/content-negotiation.md
@@ -0,0 +1,66 @@
+<a class="github" href="negotiation.py"></a>
+
+# Content negotiation
+
+> 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.
+>
+> &mdash; [RFC 2616][cite], Fielding et al.
+
+[cite]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html
+
+Content negotiation is the process of selecting one of multiple possible representations to return to a client, based on client or server preferences.
+
+## Determining the accepted renderer
+
+REST framework uses a simple style of content negotiation to determine which media type should be returned to a client, based on the available renderers, the priorities of each of those renderers, and the client's `Accept:` header. The style used is partly client-driven, and partly server-driven.
+
+1. More specific media types are given preference to less specific media types.
+2. If multiple media types have the same specificity, then preference is given to based on the ordering of the renderers configured for the given view.
+
+For example, given the following `Accept` header:
+
+ application/json; indent=4, application/json, application/yaml, text/html, */*
+
+The priorities for each of the given media types would be:
+
+* `application/json; indent=4`
+* `application/json`, `application/yaml` and `text/html`
+* `*/*`
+
+If the requested view was only configured with renderers for `YAML` and `HTML`, then REST framework would select whichever renderer was listed first in the `renderer_classes` list or `DEFAULT_RENDERER_CLASSES` setting.
+
+For more information on the `HTTP Accept` header, see [RFC 2616][accept-header]
+
+---
+
+**Note**: "q" values are not taken into account by REST framework when determining preference. The use of "q" values negatively impacts caching, and in the author's opinion they are an unnecessary and overcomplicated approach to content negotiation.
+
+This is a valid approach as the HTTP spec deliberately underspecifies how a server should weight server-based preferences against client-based preferences.
+
+---
+
+# Custom content negotiation
+
+It's unlikely that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme override `BaseContentNegotiation`.
+
+REST framework's content negotiation classes handle selection of both the appropriate parser for the request, and the appropriate renderer for the response, so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods.
+
+## Example
+
+The following is a custom content negotiation class which ignores the client
+request when selecting the appropriate parser or renderer.
+
+ class IgnoreClientContentNegotiation(BaseContentNegotiation):
+ def select_parser(self, request, parsers):
+ """
+ Select the first parser in the `.parser_classes` list.
+ """
+ return parsers[0]
+
+ def select_renderer(self, request, renderers, format_suffix):
+ """
+ Select the first renderer in the `.renderer_classes` list.
+ """
+ return renderers[0]
+
+[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md
new file mode 100644
index 00000000..ba57fde8
--- /dev/null
+++ b/docs/api-guide/exceptions.md
@@ -0,0 +1,88 @@
+<a class="github" href="exceptions.py"></a>
+
+# Exceptions
+
+> Exceptions… allow error handling to be organized cleanly in a central or high-level place within the program structure.
+>
+> &mdash; Doug Hellmann, [Python Exception Handling Techniques][cite]
+
+## Exception handling in REST framework views
+
+REST framework's views handle various exceptions, and deal with returning appropriate error responses.
+
+The handled exceptions are:
+
+* Subclasses of `APIException` raised inside REST framework.
+* Django's `Http404` exception.
+* Django's `PermissionDenied` exception.
+
+In each case, REST framework will return a response with an appropriate status code and content-type. The body of the response will include any additional details regarding the nature of the error.
+
+By default all error responses will include a key `details` in the body of the response, but other keys may also be included.
+
+For example, the following request:
+
+ DELETE http://api.example.com/foo/bar HTTP/1.1
+ Accept: application/json
+
+Might receive an error response indicating that the `DELETE` method is not allowed on that resource:
+
+ HTTP/1.1 405 Method Not Allowed
+ Content-Type: application/json; charset=utf-8
+ Content-Length: 42
+
+ {"detail": "Method 'DELETE' not allowed."}
+
+---
+
+# API Reference
+
+## APIException
+
+**Signature:** `APIException(detail=None)`
+
+The **base class** for all exceptions raised inside REST framework.
+
+To provide a custom exception, subclass `APIException` and set the `.status_code` and `.detail` properties on the class.
+
+## ParseError
+
+**Signature:** `ParseError(detail=None)`
+
+Raised if the request contains malformed data when accessing `request.DATA` or `request.FILES`.
+
+By default this exception results in a response with the HTTP status code "400 Bad Request".
+
+## PermissionDenied
+
+**Signature:** `PermissionDenied(detail=None)`
+
+Raised when an incoming request fails the permission checks.
+
+By default this exception results in a response with the HTTP status code "403 Forbidden".
+
+## MethodNotAllowed
+
+**Signature:** `MethodNotAllowed(method, detail=None)`
+
+Raised when an incoming request occurs that does not map to a handler method on the view.
+
+By default this exception results in a response with the HTTP status code "405 Method Not Allowed".
+
+## UnsupportedMediaType
+
+**Signature:** `UnsupportedMediaType(media_type, detail=None)`
+
+Raised if there are no parsers that can handle the content type of the request data when accessing `request.DATA` or `request.FILES`.
+
+By default this exception results in a response with the HTTP status code "415 Unsupported Media Type".
+
+## Throttled
+
+**Signature:** `Throttled(wait=None, detail=None)`
+
+Raised when an incoming request fails the throttling checks.
+
+By default this exception results in a response with the HTTP status code "429 Too Many Requests".
+
+[cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
new file mode 100644
index 00000000..8c3df067
--- /dev/null
+++ b/docs/api-guide/fields.md
@@ -0,0 +1,278 @@
+<a class="github" href="fields.py"></a>
+
+# Serializer fields
+
+> Flat is better than nested.
+>
+> &mdash; [The Zen of Python][cite]
+
+Serializer fields handle converting between primative values and internal datatypes. They also deal with validating input values, as well as retrieving and setting the values from their parent objects.
+
+---
+
+**Note:** The serializer fields are declared in fields.py, but by convention you should import them using `from rest_framework import serializers` and refer to fields as `serializers.<FieldName>`.
+
+---
+
+## Core arguments
+
+Each serializer field class constructor takes at least these arguments. Some Field classes take additional, field-specific arguments, but the following should always be accepted:
+
+### `source`
+
+The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `Field(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `Field(source='user.email')`.
+
+The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations. (See the implementation of the `PaginationSerializer` class for an example.)
+
+Defaults to the name of the field.
+
+### `read_only`
+
+Set this to `True` to ensure that the field is used when serializing a representation, but is not used when updating an instance dureing deserialization.
+
+Defaults to `False`
+
+### `required`
+
+Normally an error will be raised if a field is not supplied during deserialization.
+Set to false if this field is not required to be present during deserialization.
+
+Defaults to `True`.
+
+### `default`
+
+If set, this gives the default value that will be used for the field if none is supplied. If not set the default behaviour is to not populate the attribute at all.
+
+### `validators`
+
+A list of Django validators that should be used to validate deserialized values.
+
+### `error_messages`
+
+A dictionary of error codes to error messages.
+
+### `widget`
+
+Used only if rendering the field to HTML.
+This argument sets the widget that should be used to render the field.
+
+
+---
+
+# Generic Fields
+
+These generic fields are used for representing arbitrary model fields or the output of model methods.
+
+## Field
+
+A generic, **read-only** field. You can use this field for any attribute that does not need to support write operations.
+
+For example, using the following model.
+
+ class Account(models.Model):
+ owner = models.ForeignKey('auth.user')
+ name = models.CharField(max_length=100)
+ created = models.DateTimeField(auto_now_add=True)
+ payment_expiry = models.DateTimeField()
+
+ def has_expired(self):
+ now = datetime.datetime.now()
+ return now > self.payment_expiry
+
+A serializer definition that looked like this:
+
+ class AccountSerializer(serializers.HyperlinkedModelSerializer):
+ expired = Field(source='has_expired')
+
+ class Meta:
+ fields = ('url', 'owner', 'name', 'expired')
+
+Would produce output similar to:
+
+ {
+ 'url': 'http://example.com/api/accounts/3/',
+ 'owner': 'http://example.com/api/users/12/',
+ 'name': 'FooCorp business account',
+ 'expired': True
+ }
+
+By default, the `Field` class will perform a basic translation of the source value into primative datatypes, falling back to unicode representations of complex datatypes when necessary.
+
+You can customize this behaviour by overriding the `.to_native(self, value)` method.
+
+## WritableField
+
+A field that supports both read and write operations. By itself `WriteableField` does not perform any translation of input values into a given type. You won't typically use this field directly, but you may want to override it and implement the `.to_native(self, value)` and `.from_native(self, value)` methods.
+
+## ModelField
+
+A generic field that can be tied to any arbitrary model field. The `ModelField` class delegates the task of serialization/deserialization to it's associated model field. This field can be used to create serializer fields for custom model fields, without having to create a new custom serializer field.
+
+**Signature:** `ModelField(model_field=<Django ModelField class>)`
+
+---
+
+# Typed Fields
+
+These fields represent basic datatypes, and support both reading and writing values.
+
+## BooleanField
+
+A Boolean representation.
+
+Corresponds to `django.db.models.fields.BooleanField`.
+
+## CharField
+
+A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`.
+
+Corresponds to `django.db.models.fields.CharField`
+or `django.db.models.fields.TextField`.
+
+**Signature:** `CharField(max_length=None, min_length=None)`
+
+## ChoiceField
+
+A field that can accept a value out of a limited set of choices.
+
+## EmailField
+
+A text representation, validates the text to be a valid e-mail address.
+
+Corresponds to `django.db.models.fields.EmailField`
+
+## DateField
+
+A date representation.
+
+Corresponds to `django.db.models.fields.DateField`
+
+## DateTimeField
+
+A date and time representation.
+
+Corresponds to `django.db.models.fields.DateTimeField`
+
+## IntegerField
+
+An integer representation.
+
+Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField`
+
+## FloatField
+
+A floating point representation.
+
+Corresponds to `django.db.models.fields.FloatField`.
+
+---
+
+# Relational Fields
+
+Relational fields are used to represent model relationships. They can be applied to `ForeignKey`, `ManyToManyField` and `OneToOneField` relationships, as well as to reverse relationships, and custom relationships such as `GenericForeignKey`.
+
+## RelatedField
+
+This field can be applied to any of the following:
+
+* A `ForeignKey` field.
+* A `OneToOneField` field.
+* A reverse OneToOne relationship
+* Any other "to-one" relationship.
+
+By default `RelatedField` will represent the target of the field using it's `__unicode__` method.
+
+You can customise this behaviour by subclassing `ManyRelatedField`, and overriding the `.to_native(self, value)` method.
+
+## ManyRelatedField
+
+This field can be applied to any of the following:
+
+* A `ManyToManyField` field.
+* A reverse ManyToMany relationship.
+* A reverse ForeignKey relationship
+* Any other "to-many" relationship.
+
+By default `ManyRelatedField` will represent the targets of the field using their `__unicode__` method.
+
+For example, given the following models:
+
+ class TaggedItem(models.Model):
+ """
+ Tags arbitrary model instances using a generic relation.
+
+ See: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/
+ """
+ tag = models.SlugField()
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = GenericForeignKey('content_type', 'object_id')
+
+ def __unicode__(self):
+ return self.tag
+
+
+ class Bookmark(models.Model):
+ """
+ A bookmark consists of a URL, and 0 or more descriptive tags.
+ """
+ url = models.URLField()
+ tags = GenericRelation(TaggedItem)
+
+And a model serializer defined like this:
+
+ class BookmarkSerializer(serializers.ModelSerializer):
+ tags = serializers.ManyRelatedField(source='tags')
+
+ class Meta:
+ model = Bookmark
+ exclude = ('id',)
+
+Then an example output format for a Bookmark instance would be:
+
+ {
+ 'tags': [u'django', u'python'],
+ 'url': u'https://www.djangoproject.com/'
+ }
+
+## PrimaryKeyRelatedField
+
+This field can be applied to any "to-one" relationship, such as a `ForeignKey` field.
+
+`PrimaryKeyRelatedField` will represent the target of the field using it's primary key.
+
+Be default, `PrimaryKeyRelatedField` is read-write, although you can change this behaviour using the `read_only` flag.
+
+## ManyPrimaryKeyRelatedField
+
+This field can be applied to any "to-many" relationship, such as a `ManyToManyField` field, or a reverse `ForeignKey` relationship.
+
+`PrimaryKeyRelatedField` will represent the targets of the field using their primary key.
+
+Be default, `ManyPrimaryKeyRelatedField` is read-write, although you can change this behaviour using the `read_only` flag.
+
+## HyperlinkedRelatedField
+
+This field can be applied to any "to-one" relationship, such as a `ForeignKey` field.
+
+`HyperlinkedRelatedField` will represent the target of the field using a hyperlink. You must include a named URL pattern in your URL conf, with a name like `'{model-name}-detail'` that corresponds to the target of the hyperlink.
+
+Be default, `HyperlinkedRelatedField` is read-write, although you can change this behaviour using the `read_only` flag.
+
+## ManyHyperlinkedRelatedField
+
+This field can be applied to any "to-many" relationship, such as a `ManyToManyField` field, or a reverse `ForeignKey` relationship.
+
+`ManyHyperlinkedRelatedField` will represent the targets of the field using hyperlinks. You must include a named URL pattern in your URL conf, with a name like `'{model-name}-detail'` that corresponds to the target of the hyperlink.
+
+Be default, `ManyHyperlinkedRelatedField` is read-write, although you can change this behaviour using the `read_only` flag.
+
+## HyperLinkedIdentityField
+
+This field can be applied as an identity relationship, such as the `'url'` field on a HyperlinkedModelSerializer.
+
+You must include a named URL pattern in your URL conf, with a name like `'{model-name}-detail'` that corresponds to the model.
+
+This field is always read-only.
+
+[cite]: http://www.python.org/dev/peps/pep-0020/
diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md
new file mode 100644
index 00000000..6d5feba4
--- /dev/null
+++ b/docs/api-guide/format-suffixes.md
@@ -0,0 +1,61 @@
+<a class="github" href="urlpatterns.py"></a>
+
+# Format suffixes
+
+> Section 6.2.1 does not say that content negotiation should be
+used all the time.
+>
+> &mdash; Roy Fielding, [REST discuss mailing list][cite]
+
+A common pattern for Web APIs is to use filename extensions on URLs to provide an endpoint for a given media type. For example, 'http://example.com/api/users.json' to serve a JSON representation.
+
+Adding format-suffix patterns to each individual entry in the URLconf for your API is error-prone and non-DRY, so REST framework provides a shortcut to adding these patterns to your URLConf.
+
+## format_suffix_patterns
+
+**Signature**: format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None)
+
+Returns a URL pattern list which includes format suffix patterns appended to each of the URL patterns provided.
+
+Arguments:
+
+* **urlpatterns**: Required. A URL pattern list.
+* **suffix_required**: Optional. A boolean indicating if suffixes in the URLs should be optional or mandatory. Defaults to `False`, meaning that suffixes are optional by default.
+* **allowed**: Optional. A list or tuple of valid format suffixes. If not provided, a wildcard format suffix pattern will be used.
+
+Example:
+
+ from rest_framework.urlpatterns import format_suffix_patterns
+
+ urlpatterns = patterns('blog.views',
+ url(r'^/$', 'api_root'),
+ url(r'^comment/$', 'comment_root'),
+ url(r'^comment/(?P<pk>[0-9]+)/$', 'comment_instance')
+ )
+
+ urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
+
+When using `format_suffix_patterns`, you must make sure to add the `'format'` keyword argument to the corresponding views. For example.
+
+ @api_view(('GET',))
+ def api_root(request, format=None):
+ # do stuff...
+
+The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` setting.
+
+Also note that `format_suffix_patterns` does not support descending into `include` URL patterns.
+
+---
+
+## Accept headers vs. format suffixes
+
+There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead.
+
+It is actually a misconception. For example, take the following quote from Roy Fielding discussing the relative merits of query parameter media-type indicators vs. file extension media-type indicators:
+
+&ldquo;That's why I always prefer extensions. Neither choice has anything to do with REST.&rdquo; &mdash; Roy Fielding, [REST discuss mailing list][cite2]
+
+The quote does not mention Accept headers, but it does make it clear that format suffixes should be considered an acceptable pattern.
+
+[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857
+[cite2]: http://tech.groups.yahoo.com/group/rest-discuss/message/14844 \ No newline at end of file
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
new file mode 100644
index 00000000..360ef1a2
--- /dev/null
+++ b/docs/api-guide/generic-views.md
@@ -0,0 +1,187 @@
+<a class="github" href="mixins.py"></a>
+<a class="github" href="generics.py"></a>
+
+# Generic views
+
+> Django’s generic views... were developed as a shortcut for common usage patterns... They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to repeat yourself.
+>
+> &mdash; [Django Documentation][cite]
+
+One of the key benefits of class based views is the way they allow you to compose bits of reusable behaviour. REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns.
+
+The generic views provided by REST framework allow you to quickly build API views that map closely to your database models.
+
+If the generic views don't suit the needs of your API, you can drop down to using the regular `APIView` class, or reuse the mixins and base classes used by the generic views to compose your own set of reusable generic views.
+
+## Examples
+
+Typically when using the generic views, you'll override the view, and set several class attributes.
+
+ class UserList(generics.ListCreateAPIView):
+ model = User
+ serializer_class = UserSerializer
+ permission_classes = (IsAdminUser,)
+ paginate_by = 100
+
+For more complex cases you might also want to override various methods on the view class. For example.
+
+ class UserList(generics.ListCreateAPIView):
+ model = User
+ serializer_class = UserSerializer
+ permission_classes = (IsAdminUser,)
+
+ def get_paginate_by(self, queryset):
+ """
+ Use smaller pagination for HTML representations.
+ """
+ page_size_param = self.request.QUERY_PARAMS.get('page_size')
+ if page_size_param:
+ return int(page_size_param)
+ return 100
+
+For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something the following entry.
+
+ url(r'^/users/', ListCreateAPIView.as_view(model=User) name='user-list')
+
+---
+
+# API Reference
+
+The following classes are the concrete generic views. If you're using generic views this is normally the level you'll be working at unless you need heavily customized behavior.
+
+## CreateAPIView
+
+Used for **create-only** endpoints.
+
+Provides `post` method handlers.
+
+Extends: [GenericAPIView], [CreateModelMixin]
+
+## ListAPIView
+
+Used for **read-only** endpoints to represent a **collection of model instances**.
+
+Provides a `get` method handler.
+
+Extends: [MultipleObjectAPIView], [ListModelMixin]
+
+## RetrieveAPIView
+
+Used for **read-only** endpoints to represent a **single model instance**.
+
+Provides a `get` method handler.
+
+Extends: [SingleObjectAPIView], [RetrieveModelMixin]
+
+## DestroyAPIView
+
+Used for **delete-only** endpoints for a **single model instance**.
+
+Provides a `delete` method handler.
+
+Extends: [SingleObjectAPIView], [DestroyModelMixin]
+
+## UpdateAPIView
+
+Used for **update-only** endpoints for a **single model instance**.
+
+Provides a `put` method handler.
+
+Extends: [SingleObjectAPIView], [UpdateModelMixin]
+
+## ListCreateAPIView
+
+Used for **read-write** endpoints to represent a **collection of model instances**.
+
+Provides `get` and `post` method handlers.
+
+Extends: [MultipleObjectAPIView], [ListModelMixin], [CreateModelMixin]
+
+## RetrieveDestroyAPIView
+
+Used for **read or delete** endpoints to represent a **single model instance**.
+
+Provides `get` and `delete` method handlers.
+
+Extends: [SingleObjectAPIView], [RetrieveModelMixin], [DestroyModelMixin]
+
+## RetrieveUpdateDestroyAPIView
+
+Used for **read-write-delete** endpoints to represent a **single model instance**.
+
+Provides `get`, `put` and `delete` method handlers.
+
+Extends: [SingleObjectAPIView], [RetrieveModelMixin], [UpdateModelMixin], [DestroyModelMixin]
+
+---
+
+# Base views
+
+Each of the generic views provided is built by combining one of the base views below, with one or more mixin classes.
+
+## GenericAPIView
+
+Extends REST framework's `APIView` class, adding support for serialization of model instances and model querysets.
+
+## MultipleObjectAPIView
+
+Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [MultipleObjectMixin].
+
+**See also:** ccbv.co.uk documentation for [MultipleObjectMixin][multiple-object-mixin-classy].
+
+## SingleObjectAPIView
+
+Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [SingleObjectMixin].
+
+**See also:** ccbv.co.uk documentation for [SingleObjectMixin][single-object-mixin-classy].
+
+---
+
+# Mixins
+
+The mixin classes provide the actions that are used to provide the basic view behaviour. Note that the mixin classes provide action methods rather than defining the handler methods such as `.get()` and `.post()` directly. This allows for more flexible composition of behaviour.
+
+## ListModelMixin
+
+Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset.
+
+Should be mixed in with [MultipleObjectAPIView].
+
+## CreateModelMixin
+
+Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.
+
+Should be mixed in with any [GenericAPIView].
+
+## RetrieveModelMixin
+
+Provides a `.retrieve(request, *args, **kwargs)` method, that implements returning an existing model instance in a response.
+
+Should be mixed in with [SingleObjectAPIView].
+
+## UpdateModelMixin
+
+Provides a `.update(request, *args, **kwargs)` method, that implements updating and saving an existing model instance.
+
+Should be mixed in with [SingleObjectAPIView].
+
+## DestroyModelMixin
+
+Provides a `.destroy(request, *args, **kwargs)` method, that implements deletion of an existing model instance.
+
+Should be mixed in with [SingleObjectAPIView].
+
+[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views
+[MultipleObjectMixin]: https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/
+[SingleObjectMixin]: https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-single-object/
+[multiple-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.list/MultipleObjectMixin/
+[single-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.detail/SingleObjectMixin/
+
+[GenericAPIView]: #genericapiview
+[SingleObjectAPIView]: #singleobjectapiview
+[MultipleObjectAPIView]: #multipleobjectapiview
+[ListModelMixin]: #listmodelmixin
+[CreateModelMixin]: #createmodelmixin
+[RetrieveModelMixin]: #retrievemodelmixin
+[UpdateModelMixin]: #updatemodelmixin
+[DestroyModelMixin]: #destroymodelmixin \ No newline at end of file
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
new file mode 100644
index 00000000..597baba4
--- /dev/null
+++ b/docs/api-guide/pagination.md
@@ -0,0 +1,125 @@
+<a class="github" href="pagination.py"></a>
+
+# Pagination
+
+> Django provides a few classes that help you manage paginated data – that is, data that’s split across several pages, with “Previous/Next” links.
+>
+> &mdash; [Django documentation][cite]
+
+REST framework includes a `PaginationSerializer` class that makes it easy to return paginated data in a way that can then be rendered to arbitrary media types.
+
+## Paginating basic data
+
+Let's start by taking a look at an example from the Django documentation.
+
+ from django.core.paginator import Paginator
+ objects = ['john', 'paul', 'george', 'ringo']
+ paginator = Paginator(objects, 2)
+ page = paginator.page(1)
+ page.object_list
+ # ['john', 'paul']
+
+At this point we've got a page object. If we wanted to return this page object as a JSON response, we'd need to provide the client with context such as next and previous links, so that it would be able to page through the remaining results.
+
+ from rest_framework.pagination import PaginationSerializer
+ serializer = PaginationSerializer(instance=page)
+ serializer.data
+ # {'count': 4, 'next': '?page=2', 'previous': None, 'results': [u'john', u'paul']}
+
+The `context` argument of the `PaginationSerializer` class may optionally include the request. If the request is included in the context then the next and previous links returned by the serializer will use absolute URLs instead of relative URLs.
+
+ request = RequestFactory().get('/foobar')
+ serializer = PaginationSerializer(instance=page, context={'request': request})
+ serializer.data
+ # {'count': 4, 'next': 'http://testserver/foobar?page=2', 'previous': None, 'results': [u'john', u'paul']}
+
+We could now return that data in a `Response` object, and it would be rendered into the correct media type.
+
+## Paginating QuerySets
+
+Our first example worked because we were using primative objects. If we wanted to paginate a queryset or other complex data, we'd need to specify a serializer to use to serialize the result set itself with.
+
+We can do this using the `object_serializer_class` attribute on the inner `Meta` class of the pagination serializer. For example.
+
+ class UserSerializer(serializers.ModelSerializer):
+ """
+ Serializes user querysets.
+ """
+ class Meta:
+ model = User
+ fields = ('username', 'email')
+
+ class PaginatedUserSerializer(pagination.PaginationSerializer):
+ """
+ Serializes page objects of user querysets.
+ """
+ class Meta:
+ object_serializer_class = UserSerializer
+
+We could now use our pagination serializer in a view like this.
+
+ @api_view('GET')
+ def user_list(request):
+ queryset = User.objects.all()
+ paginator = Paginator(queryset, 20)
+
+ page = request.QUERY_PARAMS.get('page')
+ try:
+ users = paginator.page(page)
+ except PageNotAnInteger:
+ # If page is not an integer, deliver first page.
+ users = paginator.page(1)
+ except EmptyPage:
+ # If page is out of range (e.g. 9999), deliver last page of results.
+ users = paginator.page(paginator.num_pages)
+
+ serializer_context = {'request': request}
+ serializer = PaginatedUserSerializer(instance=users,
+ context=serializer_context)
+ return Response(serializer.data)
+
+## Pagination in the generic views
+
+The generic class based views `ListAPIView` and `ListCreateAPIView` provide pagination of the returned querysets by default. You can customise this behaviour by altering the pagination style, by modifying the default number of results, or by turning pagination off completely.
+
+The default pagination style may be set globally, using the `PAGINATION_SERIALIZER` and `PAGINATE_BY` settings. For example.
+
+ REST_FRAMEWORK = {
+ 'PAGINATION_SERIALIZER': (
+ 'example_app.pagination.CustomPaginationSerializer',
+ ),
+ 'PAGINATE_BY': 10
+ }
+
+You can also set the pagination style on a per-view basis, using the `ListAPIView` generic class-based view.
+
+ class PaginatedListView(ListAPIView):
+ model = ExampleModel
+ pagination_serializer_class = CustomPaginationSerializer
+ paginate_by = 10
+
+For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods.
+
+---
+
+# Custom pagination serializers
+
+To create a custom pagination serializer class you should override `pagination.BasePaginationSerializer` and set the fields that you want the serializer to return.
+
+You can also override the name used for the object list field, by setting the `results_field` attribute, which defaults to `'results'`.
+
+## Example
+
+For example, to nest a pair of links labelled 'prev' and 'next', and set the name for the results field to 'objects', you might use something like this.
+
+ class LinksSerializer(serializers.Serializer):
+ next = pagination.NextURLField(source='*')
+ prev = pagination.PreviousURLField(source='*')
+
+ class CustomPaginationSerializer(pagination.BasePaginationSerializer):
+ links = LinksSerializer(source='*') # Takes the page object as the source
+ total_results = serializers.Field(source='paginator.count')
+
+ results_field = 'objects'
+
+[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/
diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md
new file mode 100644
index 00000000..59f00f99
--- /dev/null
+++ b/docs/api-guide/parsers.md
@@ -0,0 +1,161 @@
+<a class="github" href="parsers.py"></a>
+
+# Parsers
+
+> Machine interacting web services tend to use more
+structured formats for sending data than form-encoded, since they're
+sending more complex data than simple forms
+>
+> &mdash; Malcom Tredinnick, [Django developers group][cite]
+
+REST framework includes a number of built in Parser classes, that allow you to accept requests with various media types. There is also support for defining your own custom parsers, which gives you the flexibility to design the media types that your API accepts.
+
+## How the parser is determined
+
+The set of valid parsers for a view is always defined as a list of classes. When either `request.DATA` or `request.FILES` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content.
+
+## Setting the parsers
+
+The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `YAML` content.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework.parsers.YAMLParser',
+ )
+ }
+
+You can also set the renderers used for an individual view, using the `APIView` class based views.
+
+ class ExampleView(APIView):
+ """
+ A view that can accept POST requests with YAML content.
+ """
+ parser_classes = (YAMLParser,)
+
+ def post(self, request, format=None):
+ return Response({'received data': request.DATA})
+
+Or, if you're using the `@api_view` decorator with function based views.
+
+ @api_view(['POST'])
+ @parser_classes((YAMLParser,))
+ def example_view(request, format=None):
+ """
+ A view that can accept POST requests with YAML content.
+ """
+ return Response({'received data': request.DATA})
+
+---
+
+# API Reference
+
+## JSONParser
+
+Parses `JSON` request content.
+
+**.media_type**: `application/json`
+
+## YAMLParser
+
+Parses `YAML` request content.
+
+**.media_type**: `application/yaml`
+
+## XMLParser
+
+Parses REST framework's default style of `XML` request content.
+
+Note that the `XML` markup language is typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`.
+
+If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type.
+
+**.media_type**: `application/xml`
+
+## FormParser
+
+Parses HTML form content. `request.DATA` will be populated with a `QueryDict` of data, `request.FILES` will be populated with an empty `QueryDict` of data.
+
+You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data.
+
+**.media_type**: `application/x-www-form-urlencoded`
+
+## MultiPartParser
+
+Parses multipart HTML form content, which supports file uploads. Both `request.DATA` and `request.FILES` will be populated with a `QueryDict`.
+
+You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data.
+
+**.media_type**: `multipart/form-data`
+
+---
+
+# Custom parsers
+
+To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, media_type, parser_context)` method.
+
+The method should return the data that will be used to populate the `request.DATA` property.
+
+The arguments passed to `.parse()` are:
+
+### stream
+
+A stream-like object representing the body of the request.
+
+### media_type
+
+Optional. If provided, this is the media type of the incoming request content.
+
+Depending on the request's `Content-Type:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"text/plain; charset=utf-8"`.
+
+### parser_context
+
+Optional. If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content.
+
+By default this will include the following keys: `view`, `request`, `args`, `kwargs`.
+
+## Example
+
+The following is an example plaintext parser that will populate the `request.DATA` property with a string representing the body of the request.
+
+ class PlainTextParser(BaseParser):
+ """
+ Plain text parser.
+ """
+
+ media_type = 'text/plain'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Simply return a string representing the body of the request.
+ """
+ return stream.read()
+
+## Uploading file content
+
+If your custom parser needs to support file uploads, you may return a `DataAndFiles` object from the `.parse()` method. `DataAndFiles` should be instantiated with two arguments. The first argument will be used to populate the `request.DATA` property, and the second argument will be used to populate the `request.FILES` property.
+
+For example:
+
+ class SimpleFileUploadParser(BaseParser):
+ """
+ A naive raw file upload parser.
+ """
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ content = stream.read()
+ name = 'example.dat'
+ content_type = 'application/octet-stream'
+ size = len(content)
+ charset = 'utf-8'
+
+ # Write a temporary file based on the request content
+ temp = tempfile.NamedTemporaryFile(delete=False)
+ temp.write(content)
+ uploaded = UploadedFile(temp, name, content_type, size, charset)
+
+ # Return the uploaded file
+ data = {}
+ files = {name: uploaded}
+ return DataAndFiles(data, files)
+
+[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md
new file mode 100644
index 00000000..1a746fb6
--- /dev/null
+++ b/docs/api-guide/permissions.md
@@ -0,0 +1,118 @@
+<a class="github" href="permissions.py"></a>
+
+# Permissions
+
+> Authentication or identification by itself is not usually sufficient to gain access to information or code. For that, the entity requesting access must have authorization.
+>
+> &mdash; [Apple Developer Documentation][cite]
+
+Together with [authentication] and [throttling], permissions determine whether a request should be granted or denied access.
+
+Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted.
+
+## How permissions are determined
+
+Permissions in REST framework are always defined as a list of permission classes.
+
+Before running the main body of the view each permission in the list is checked.
+If any permission check fails an `exceptions.PermissionDenied` exception will be raised, and the main body of the view will not run.
+
+## Object level permissions
+
+REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance.
+
+Object level permissions are run by REST framework's generic views when `.get_object()` is called. As with view level permissions, an `exceptions.PermissionDenied` exception will be raised if the user is not allowed to act on the given object.
+
+## Setting the permission policy
+
+The default permission policy may be set globally, using the `DEFAULT_PERMISSION_CLASSES` setting. For example.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.IsAuthenticated',
+ )
+ }
+
+If not specified, this setting defaults to allowing unrestricted access:
+
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.AllowAny',
+ )
+
+You can also set the authentication policy on a per-view basis, using the `APIView` class based views.
+
+ class ExampleView(APIView):
+ permission_classes = (IsAuthenticated,)
+
+ def get(self, request, format=None):
+ content = {
+ 'status': 'request was permitted'
+ }
+ return Response(content)
+
+Or, if you're using the `@api_view` decorator with function based views.
+
+ @api_view('GET')
+ @permission_classes(IsAuthenticated)
+ def example_view(request, format=None):
+ content = {
+ 'status': 'request was permitted'
+ }
+ return Response(content)
+
+---
+
+# API Reference
+
+## AllowAny
+
+The `AllowAny` permission class will allow unrestricted access, **regardless of if the request was authenticated or unauthenticated**.
+
+This permission is not strictly required, since you can achieve the same result by using an empty list or tuple for the permissions setting, but you may find it useful to specify this class because it makes the intention explicit.
+
+## IsAuthenticated
+
+The `IsAuthenticated` permission class will deny permission to any unauthenticated user, and allow permission otherwise.
+
+This permission is suitable if you want your API to only be accessible to registered users.
+
+## IsAdminUser
+
+The `IsAdminUser` permission class will deny permission to any user, unless `user.is_staff` is `True` in which case permission will be allowed.
+
+This permission is suitable is you want your API to only be accessible to a subset of trusted administrators.
+
+## IsAuthenticatedOrReadOnly
+
+The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`.
+
+This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users.
+
+## DjangoModelPermissions
+
+This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. When applied to a view that has a `.model` property, authorization will only be granted if the user has the relevant model permissions assigned.
+
+* `POST` requests require the user to have the `add` permission on the model.
+* `PUT` and `PATCH` requests require the user to have the `change` permission on the model.
+* `DELETE` requests require the user to have the `delete` permission on the model.
+
+The default behaviour can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests.
+
+To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details.
+
+The `DjangoModelPermissions` class also supports object-level permissions. Third-party authorization backends such as [django-guardian][guardian] that provide object-level permissions should work just fine with `DjangoModelPermissions` without any custom configuration required.
+
+---
+
+# Custom permissions
+
+To implement a custom permission, override `BasePermission` and implement the `.has_permission(self, request, view, obj=None)` method.
+
+The method should return `True` if the request should be granted access, and `False` otherwise.
+
+
+[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
+[authentication]: authentication.md
+[throttling]: throttling.md
+[contribauth]: https://docs.djangoproject.com/en/1.0/topics/auth/#permissions
+[guardian]: https://github.com/lukaszb/django-guardian
diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
new file mode 100644
index 00000000..c3d12ddb
--- /dev/null
+++ b/docs/api-guide/renderers.md
@@ -0,0 +1,267 @@
+<a class="github" href="renderers.py"></a>
+
+# Renderers
+
+> Before a TemplateResponse instance can be returned to the client, it must be rendered. The rendering process takes the intermediate representation of template and context, and turns it into the final byte stream that can be served to the client.
+>
+> &mdash; [Django documentation][cite]
+
+REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types. There is also support for defining your own custom renderers, which gives you the flexibility to design your own media types.
+
+## How the renderer is determined
+
+The set of valid renderers for a view is always defined as a list of classes. When a view is entered REST framework will perform content negotiation on the incoming request, and determine the most appropriate renderer to satisfy the request.
+
+The basic process of content negotiation involves examining the request's `Accept` header, to determine which media types it expects in the response. Optionally, format suffixes on the URL may be used to explicitly request a particular representation. For example the URL `http://example.com/api/users_count.json` might be an endpoint that always returns JSON data.
+
+For more information see the documentation on [content negotation][conneg].
+
+## Setting the renderers
+
+The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting. For example, the following settings would use `YAML` as the main media type and also include the self describing API.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework.renderers.YAMLRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ )
+ }
+
+You can also set the renderers used for an individual view, using the `APIView` class based views.
+
+ class UserCountView(APIView):
+ """
+ A view that returns the count of active users, in JSON or JSONp.
+ """
+ renderer_classes = (JSONRenderer, JSONPRenderer)
+
+ def get(self, request, format=None):
+ user_count = User.objects.filter(active=True).count()
+ content = {'user_count': user_count}
+ return Response(content)
+
+Or, if you're using the `@api_view` decorator with function based views.
+
+ @api_view(['GET'])
+ @renderer_classes((JSONRenderer, JSONPRenderer))
+ def user_count_view(request, format=None):
+ """
+ A view that returns the count of active users, in JSON or JSONp.
+ """
+ user_count = User.objects.filter(active=True).count()
+ content = {'user_count': user_count}
+ return Response(content)
+
+## Ordering of renderer classes
+
+It's important when specifying the renderer classes for your API to think about what priority you want to assign to each media type. If a client underspecifies the representations it can accept, such as sending an `Accept: */*` header, or not including an `Accept` header at all, then REST framework will select the first renderer in the list to use for the response.
+
+For example if your API serves JSON responses and the HTML browseable API, you might want to make `JSONRenderer` your default renderer, in order to send `JSON` responses to clients that do not specify an `Accept` header.
+
+If your API includes views that can serve both regular webpages and API responses depending on the request, then you might consider making `TemplateHTMLRenderer` your default renderer, in order to play nicely with older browsers that send [broken accept headers][browser-accept-headers].
+
+---
+
+# API Reference
+
+## JSONRenderer
+
+Renders the request data into `JSON`.
+
+The client may additionally include an `'indent'` media type parameter, in which case the returned `JSON` will be indented. For example `Accept: application/json; indent=4`.
+
+**.media_type**: `application/json`
+
+**.format**: `'.json'`
+
+## JSONPRenderer
+
+Renders the request data into `JSONP`. The `JSONP` media type provides a mechanism of allowing cross-domain AJAX requests, by wrapping a `JSON` response in a javascript callback.
+
+The javascript callback function must be set by the client including a `callback` URL query parameter. For example `http://example.com/api/users?callback=jsonpCallback`. If the callback function is not explicitly set by the client it will default to `'callback'`.
+
+**Note**: If you require cross-domain AJAX requests, you may also want to consider using [CORS] as an alternative to `JSONP`.
+
+**.media_type**: `application/javascript`
+
+**.format**: `'.jsonp'`
+
+## YAMLRenderer
+
+Renders the request data into `YAML`.
+
+**.media_type**: `application/yaml`
+
+**.format**: `'.yaml'`
+
+## XMLRenderer
+
+Renders REST framework's default style of `XML` response content.
+
+Note that the `XML` markup language is used typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`.
+
+If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type.
+
+**.media_type**: `application/xml`
+
+**.format**: `'.xml'`
+
+## TemplateHTMLRenderer
+
+Renders data to HTML, using Django's standard template rendering.
+Unlike other renderers, the data passed to the `Response` does not need to be serialized. Also, unlike other renderers, you may want to include a `template_name` argument when creating the `Response`.
+
+The TemplateHTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context.
+
+The template name is determined by (in order of preference):
+
+1. An explicit `.template_name` attribute set on the response.
+2. An explicit `.template_name` attribute set on this class.
+3. The return result of calling `view.get_template_names()`.
+
+An example of a view that uses `TemplateHTMLRenderer`:
+
+ class UserInstance(generics.RetrieveUserAPIView):
+ """
+ A view that returns a templated HTML representations of a given user.
+ """
+ model = Users
+ renderer_classes = (TemplateHTMLRenderer,)
+
+ def get(self, request, *args, **kwargs)
+ self.object = self.get_object()
+ return Response({'user': self.object}, template_name='user_detail.html')
+
+You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
+
+If you're building websites that use `TemplateHTMLRenderer` along with other renderer classes, you should consider listing `TemplateHTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers.
+
+**.media_type**: `text/html`
+
+**.format**: `'.html'`
+
+See also: `StaticHTMLRenderer`
+
+## StaticHTMLRenderer
+
+A simple renderer that simply returns pre-rendered HTML. Unlike other renderers, the data passed to the response object should be a string representing the content to be returned.
+
+An example of a view that uses `TemplateHTMLRenderer`:
+
+ @api_view(('GET',))
+ @renderer_classes((StaticHTMLRenderer,))
+ def simple_html_view(request):
+ data = '<html><body><h1>Hello, world</h1></body></html>'
+ return Response(data)
+
+You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
+
+**.media_type**: `text/html`
+
+**.format**: `'.html'`
+
+See also: `TemplateHTMLRenderer`
+
+## BrowsableAPIRenderer
+
+Renders data into HTML for the Browseable API. This renderer will determine which other renderer would have been given highest priority, and use that to display an API style response within the HTML page.
+
+**.media_type**: `text/html`
+
+**.format**: `'.api'`
+
+---
+
+# Custom renderers
+
+To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, media_type=None, renderer_context=None)` method.
+
+The arguments passed to the `.render()` method are:
+
+### `data`
+
+The request data, as set by the `Response()` instantiation.
+
+### `media_type=None`
+
+Optional. If provided, this is the accepted media type, as determined by the content negotiation stage.
+
+Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; nested=true"`.
+
+### `renderer_context=None`
+
+Optional. If provided, this is a dictionary of contextual information provided by the view.
+
+By default this will include the following keys: `view`, `request`, `response`, `args`, `kwargs`.
+
+## Example
+
+The following is an example plaintext renderer that will return a response with the `data` parameter as the content of the response.
+
+ from django.utils.encoding import smart_unicode
+ from rest_framework import renderers
+
+
+ class PlainText(renderers.BaseRenderer):
+ media_type = 'text/plain'
+ format = 'txt'
+
+ def render(self, data, media_type=None, renderer_context=None):
+ if isinstance(data, basestring):
+ return data
+ return smart_unicode(data)
+
+---
+
+# Advanced renderer usage
+
+You can do some pretty flexible things using REST framework's renderers. Some examples...
+
+* Provide either flat or nested representations from the same endpoint, depending on the requested media type.
+* Serve both regular HTML webpages, and JSON based API responses from the same endpoints.
+* Specify multiple types of HTML representation for API clients to use.
+* Underspecify a renderer's media type, such as using `media_type = 'image/*'`, and use the `Accept` header to vary the encoding of the response.
+
+## Varying behaviour by media type
+
+In some cases you might want your view to use different serialization styles depending on the accepted media type. If you need to do this you can access `request.accepted_renderer` to determine the negotiated renderer that will be used for the response.
+
+For example:
+
+ @api_view(('GET',))
+ @renderer_classes((TemplateHTMLRenderer, JSONRenderer))
+ def list_users(request):
+ """
+ A view that can return JSON or HTML representations
+ of the users in the system.
+ """
+ queryset = Users.objects.filter(active=True)
+
+ if request.accepted_renderer.format == 'html':
+ # TemplateHTMLRenderer takes a context dict,
+ # and additionally requires a 'template_name'.
+ # It does not require serialization.
+ data = {'users': queryset}
+ return Response(data, template_name='list_users.html')
+
+ # JSONRenderer requires serialized data as normal.
+ serializer = UserSerializer(instance=queryset)
+ data = serializer.data
+ return Response(data)
+
+## Designing your media types
+
+For the purposes of many Web APIs, simple `JSON` responses with hyperlinked relations may be sufficient. If you want to fully embrace RESTful design and [HATEOAS] you'll need to consider the design and usage of your media types in more detail.
+
+In [the words of Roy Fielding][quote], "A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types.".
+
+For good examples of custom media types, see GitHub's use of a custom [application/vnd.github+json] media type, and Mike Amundsen's IANA approved [application/vnd.collection+json] JSON-based hypermedia.
+
+[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
+[conneg]: content-negotiation.md
+[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
+[CORS]: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
+[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
+[application/vnd.github+json]: http://developer.github.com/v3/media/
+[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md
new file mode 100644
index 00000000..72932f5d
--- /dev/null
+++ b/docs/api-guide/requests.md
@@ -0,0 +1,128 @@
+<a class="github" href="request.py"></a>
+
+# Requests
+
+> If you're doing REST-based web service stuff ... you should ignore request.POST.
+>
+> &mdash; Malcom Tredinnick, [Django developers group][cite]
+
+REST framework's `Request` class extends the standard `HttpRequest`, adding support for REST framework's flexible request parsing and request authentication.
+
+---
+
+# Request parsing
+
+REST framework's Request objects provide flexible request parsing that allows you to treat requests with JSON data or other media types in the same way that you would normally deal with form data.
+
+## .DATA
+
+`request.DATA` returns the parsed content of the request body. This is similar to the standard `request.POST` attribute except that:
+
+* It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests.
+* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming JSON data in the same way that you handle incoming form data.
+
+For more details see the [parsers documentation].
+
+## .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 is used for `request.DATA`.
+
+For more details see the [parsers documentation].
+
+## .QUERY_PARAMS
+
+`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`.
+
+For clarity inside your code, we recommend using `request.QUERY_PARAMS` instead of the usual `request.GET`, as *any* HTTP method type may include query parameters.
+
+## .parsers
+
+The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSER_CLASSES` setting.
+
+You won't typically need to access this property.
+
+---
+
+**Note:** If a client sends malformed content, then accessing `request.DATA` or `request.FILES` may raise a `ParseError`. By default REST framework's `APIView` class or `@api_view` decorator will catch the error and return a `400 Bad Request` response.
+
+If a client sends a request with a content-type that cannot be parsed then a `UnsupportedMediaType` exception will be raised, which by default will be caught and return a `415 Unsupported Media Type` response.
+
+---
+
+# Authentication
+
+REST framework provides flexible, per-request authentication, that gives you the ability to:
+
+* Use different authentication policies for different parts of your API.
+* Support the use of multiple authentication policies.
+* Provide both user and token information associated with the incoming request.
+
+## .user
+
+`request.user` typically returns an instance of `django.contrib.auth.models.User`, although the behavior depends on the authentication policy being used.
+
+If the request is unauthenticated the default value of `request.user` is an instance of `django.contrib.auth.models.AnonymousUser`.
+
+For more details see the [authentication documentation].
+
+## .auth
+
+`request.auth` returns any additional authentication context. The exact behavior of `request.auth` depends on the authentication policy being used, but it may typically be an instance of the token that the request was authenticated against.
+
+If the request is unauthenticated, or if no additional context is present, the default value of `request.auth` is `None`.
+
+For more details see the [authentication documentation].
+
+## .authenticators
+
+The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Authentication` instances, based on the `authentication_classes` set on the view or based on the `DEFAULT_AUTHENTICATORS` setting.
+
+You won't typically need to access this property.
+
+---
+
+# Browser enhancements
+
+REST framework supports a few browser enhancements such as browser-based `PUT` and `DELETE` forms.
+
+## .method
+
+`request.method` returns the **uppercased** string representation of the request's HTTP method.
+
+Browser-based `PUT` and `DELETE` forms are transparently supported.
+
+For more information see the [browser enhancements documentation].
+
+## .content_type
+
+`request.content_type`, returns a string object representing the media type of the HTTP request's body, or an empty string if no media type was provided.
+
+You won't typically need to directly access the request's content type, as you'll normally rely on REST framework's default request parsing behavior.
+
+If you do need to access the content type of the request you should use the `.content_type` property in preference to using `request.META.get('HTTP_CONTENT_TYPE')`, as it provides transparent support for browser-based non-form content.
+
+For more information see the [browser enhancements documentation].
+
+## .stream
+
+`request.stream` returns a stream representing the content of the request body.
+
+You won't typically need to directly access the request's content, as you'll normally rely on REST framework's default request parsing behavior.
+
+If you do need to access the raw content directly, you should use the `.stream` property in preference to using `request.content`, as it provides transparent support for browser-based non-form content.
+
+For more information see the [browser enhancements documentation].
+
+---
+
+# Standard HttpRequest attributes
+
+As REST framework's `Request` extends Django's `HttpRequest`, all the other standard attributes and methods are also available. For example the `request.META` dictionary is available as normal.
+
+Note that due to implementation reasons the `Request` class does not inherit from `HttpRequest` class, but instead extends the class using composition.
+
+
+[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
+[parsers documentation]: parsers.md
+[authentication documentation]: authentication.md
+[browser enhancements documentation]: ../topics/browser-enhancements.md
diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md
new file mode 100644
index 00000000..794f9377
--- /dev/null
+++ b/docs/api-guide/responses.md
@@ -0,0 +1,94 @@
+<a class="github" href="response.py"></a>
+
+# Responses
+
+> 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.
+>
+> &mdash; [Django documentation][cite]
+
+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 `SimpleTemplateResponse`. `Response` objects are initialised with data, which should consist of native python primatives. 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 provides a nicer interface for returning Web API responses.
+
+Unless you want to heavily customize REST framework for some reason, you should always use an `APIView` class or `@api_view` function for views that return `Response` objects. Doing so ensures that the view can perform content negotiation and select the appropriate renderer for the response, before it is returned from the view.
+
+---
+
+# Creating responses
+
+## Response()
+
+**Signature:** `Response(data, status=None, template_name=None, headers=None)`
+
+Unlike regular `HttpResponse` objects, you do not instantiate `Response` objects with rendered content. Instead you pass in unrendered data, which may consist of any python primatives.
+
+The renderers used by the `Response` class cannot natively handle complex datatypes such as Django model instances, so you need to serialize the data into primative datatypes before creating the `Response` object.
+
+You can use REST framework's `Serializer` classes to perform this data serialization, or use your own custom serialization.
+
+Arguments:
+
+* `data`: The serialized data for the response.
+* `status`: A status code for the response. Defaults to 200. See also [status codes][statuscodes].
+* `template_name`: A template name to use if `HTMLRenderer` is selected.
+* `headers`: A dictionary of HTTP headers to use in the response.
+
+---
+
+# Attributes
+
+## .data
+
+The unrendered content of a `Request` object.
+
+## .status_code
+
+The numeric status code of the HTTP response.
+
+## .content
+
+The rendered content of the response. The `.render()` method must have been called before `.content` can be accessed.
+
+## .template_name
+
+The `template_name`, if supplied. Only required if `HTMLRenderer` or some other custom template renderer is the accepted renderer for the reponse.
+
+## .accepted_renderer
+
+The renderer instance that will be used to render the response.
+
+Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
+
+## .accepted_media_type
+
+The media type that was selected by the content negotiation stage.
+
+Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
+
+## .renderer_context
+
+A dictionary of additional context information that will be passed to the renderer's `.render()` method.
+
+Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
+
+---
+
+# Standard HttpResponse attributes
+
+The `Response` class extends `SimpleTemplateResponse`, and all the usual attributes and methods are also available on the response. For example you can set headers on the response in the standard way:
+
+ response = Response()
+ response['Cache-Control'] = 'no-cache'
+
+## .render()
+
+**Signature:** `.render()`
+
+As with any other `TemplateResponse`, this method is called to render the serialized data of the response into the final response content. When `.render()` is called, the response content will be set to the result of calling the `.render(data, accepted_media_type, renderer_context)` method on the `accepted_renderer` instance.
+
+You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle.
+
+[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
+[statuscodes]: status-codes.md
diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md
new file mode 100644
index 00000000..19930dc3
--- /dev/null
+++ b/docs/api-guide/reverse.md
@@ -0,0 +1,55 @@
+<a class="github" href="reverse.py"></a>
+
+# Returning URLs
+
+> The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components.
+>
+> &mdash; Roy Fielding, [Architectural Styles and the Design of Network-based Software Architectures][cite]
+
+As a rule, it's probably better practice to return absolute URIs from your Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/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 makes it easy to do things like markup HTML representations with hyperlinks.
+
+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
+
+**Signature:** `reverse(viewname, *args, **kwargs)`
+
+Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
+
+You should **include the request as a keyword argument** to the function, for example:
+
+ import datetime
+ from rest_framework.reverse import reverse
+ from rest_framework.views import APIView
+
+ class APIRootView(APIView):
+ def get(self, request):
+ year = datetime.datetime.now().year
+ data = {
+ ...
+ 'year-summary-url': reverse('year-summary', args=[year], request=request)
+ }
+ return Response(data)
+
+## reverse_lazy
+
+**Signature:** `reverse_lazy(viewname, *args, **kwargs)`
+
+Has the same behavior as [`django.core.urlresolvers.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
+
+As with the `reverse` function, you should **include the request as a keyword argument** to the function, for example:
+
+ api_root = reverse_lazy('api-root', request=request)
+
+[cite]: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
+[reverse]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
+[reverse-lazy]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
new file mode 100644
index 00000000..c88b9b0c
--- /dev/null
+++ b/docs/api-guide/serializers.md
@@ -0,0 +1,276 @@
+<a class="github" href="serializers.py"></a>
+
+# 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.
+>
+> &mdash; Russell Keith-Magee, [Django users group][cite]
+
+Serializers allow complex data such as querysets and model instances to be converted to native python datatypes that can then be easily rendered into `JSON`, `XML` or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
+
+REST framework's serializers work very similarly to Django's `Form` and `ModelForm` classes. It provides a `Serializer` class which gives you a powerful, generic way to control the output of your responses, as well as a `ModelSerializer` class which provides a useful shortcut for creating serializers that deal with model instances and querysets.
+
+## Declaring Serializers
+
+Let's start by creating a simple object we can use for example purposes:
+
+ class Comment(object):
+ def __init__(self, email, content, created=None):
+ self.email = email
+ self.content = content
+ self.created = created or datetime.datetime.now()
+
+ comment = Comment(email='leila@example.com', content='foo bar')
+
+We'll declare a serializer that we can use to serialize and deserialize `Comment` objects.
+Declaring a serializer looks very similar to declaring a form:
+
+ class CommentSerializer(serializers.Serializer):
+ email = serializers.EmailField()
+ content = serializers.CharField(max_length=200)
+ created = serializers.DateTimeField()
+
+ def restore_object(self, attrs, instance=None):
+ if instance:
+ instance.title = attrs['title']
+ instance.content = attrs['content']
+ instance.created = attrs['created']
+ return instance
+ return 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. The `restore_object` method is optional, and is only required if we want our serializer to support deserialization.
+
+## Serializing objects
+
+We can now use `CommentSerializer` to serialize a comment, or list of comments. Again, using the `Serializer` class looks a lot like using a `Form` class.
+
+ serializer = CommentSerializer(instance=comment)
+ serializer.data
+ # {'email': u'leila@example.com', 'content': u'foo bar', '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(data)
+ stream
+ # '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}'
+
+## Deserializing objects
+
+Deserialization is similar. First we parse a stream into python native datatypes...
+
+ data = JSONParser().parse(stream)
+
+...then we restore those native datatypes into a fully populated object instance.
+
+ serializer = CommentSerializer(data)
+ serializer.is_valid()
+ # True
+ serializer.object
+ # <Comment object at 0x10633b2d0>
+ >>> serializer.deserialize('json', stream)
+
+## Validation
+
+When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
+
+### Field-level validation
+
+You can specify custom field-level validation by adding `validate_<fieldname>()` methods to your `Serializer` subclass. These are analagous to `clean_<fieldname>` methods on Django forms, but accept slightly different arguments. They take a dictionary of deserialized attributes as a first argument, and the field name in that dictionary as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). Your `validate_<fieldname>` methods should either just return the attrs dictionary or raise a `ValidationError`. For example:
+
+ from rest_framework import serializers
+
+ class BlogPostSerializer(serializers.Serializer):
+ title = serializers.CharField(max_length=100)
+ content = serializers.CharField()
+
+ def validate_title(self, attrs, source):
+ """
+ Check that the blog post is about Django
+ """
+ value = attrs[source]
+ if "Django" not in value:
+ raise serializers.ValidationError("Blog post is not about Django")
+ return attrs
+
+### Final cross-field validation
+
+To do any other validation that requires access to multiple fields, add a method called `validate` to your `Serializer` subclass. This method takes a single argument, which is the `attrs` dictionary. It should raise a `ValidationError` if necessary, or just return `attrs`.
+
+## Dealing with nested objects
+
+The previous example is fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects,
+where some of the attributes of an object might not be simple datatypes such as strings, dates or integers.
+
+The `Serializer` class is itself a type of `Field`, and can be used to represent relationships where one object type is nested inside another.
+
+ class UserSerializer(serializers.Serializer):
+ email = serializers.Field()
+ username = serializers.Field()
+
+ class CommentSerializer(serializers.Serializer):
+ user = UserSerializer()
+ title = serializers.Field()
+ content = serializers.Field()
+ created = serializers.Field()
+
+---
+
+**Note**: Nested serializers are only suitable for read-only representations, as there are cases where they would have ambiguous or non-obvious behavior if used when updating instances. For read-write representations you should always use a flat representation, by using one of the `RelatedField` subclasses.
+
+---
+
+
+## Creating custom fields
+
+If you want to create a custom field, you'll probably want to override either one or both of the `.to_native()` and `.from_native()` methods. These two methods are used to convert between the intial datatype, and a primative, serializable datatype. Primative datatypes may be any of a number, string, date/time/datetime or None. They may also be any list or dictionary like object that only contains other primative objects.
+
+The `.to_native()` method is called to convert the initial datatype into a primative, serializable datatype. The `from_native()` method is called to restore a primative datatype into it's initial representation.
+
+Let's look at an example of serializing a class that represents an RGB color value:
+
+ class Color(object):
+ """
+ A color represented in the RGB colorspace.
+ """
+ def __init__(self, red, green, blue):
+ assert(red >= 0 and green >= 0 and blue >= 0)
+ assert(red < 256 and green < 256 and blue < 256)
+ self.red, self.green, self.blue = red, green, blue
+
+ class ColourField(serializers.WritableField):
+ """
+ Color objects are serialized into "rgb(#, #, #)" notation.
+ """
+ def to_native(self, obj):
+ return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)
+
+ def from_native(self, data):
+ data = data.strip('rgb(').rstrip(')')
+ red, green, blue = [int(col) for col in data.split(',')]
+ return Color(red, green, blue)
+
+
+By default field values are treated as mapping to an attribute on the object. If you need to customize how the field value is accessed and set you need to override `.field_to_native()` and/or `.field_from_native()`.
+
+As an example, let's create a field that can be used represent the class name of the object being serialized:
+
+ class ClassNameField(serializers.WritableField):
+ def field_to_native(self, obj, field_name):
+ """
+ Serialize the object's class name, not an attribute of the object.
+ """
+ return obj.__class__.__name__
+
+ def field_from_native(self, data, field_name, into):
+ """
+ We don't want to set anything when we revert this field.
+ """
+ pass
+
+---
+
+# ModelSerializers
+
+Often you'll want serializer classes that map closely to model definitions.
+The `ModelSerializer` class lets you automatically create a Serializer class with fields that corrospond to the Model fields.
+
+ class AccountSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Account
+
+**[TODO: Explain model field to serializer field mapping in more detail]**
+
+## Specifying fields explicitly
+
+You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class.
+
+ class AccountSerializer(serializers.ModelSerializer):
+ url = CharField(source='get_absolute_url', read_only=True)
+ group = NaturalKeyField()
+
+ class Meta:
+ model = Account
+
+Extra fields can correspond to any property or callable on the model.
+
+## Relational fields
+
+When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation is to use the primary keys of the related instances.
+
+Alternative representations include serializing using natural keys, serializing complete nested representations, or serializing using a custom representation, such as a URL that uniquely identifies the model instances.
+
+The `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` fields provide alternative flat representations.
+
+The `ModelSerializer` class can itself be used as a field, in order to serialize relationships using nested representations.
+
+The `RelatedField` class may be subclassed to create a custom representation of a relationship. The subclass should override `.to_native()`, and optionally `.from_native()` if deserialization is supported.
+
+All the relational fields may be used for any relationship or reverse relationship on a model.
+
+## Specifying which fields should be included
+
+If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
+
+For example:
+
+ class AccountSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Account
+ exclude = ('id',)
+
+## Specifiying nested serialization
+
+The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option:
+
+ class AccountSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Account
+ exclude = ('id',)
+ depth = 1
+
+The `depth` option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
+
+## Customising the default fields
+
+You can create customized subclasses of `ModelSerializer` that use a different set of default fields for the representation, by overriding various `get_<field_type>_field` methods.
+
+Each of these methods may either return a field or serializer instance, or `None`.
+
+### get_pk_field
+
+**Signature**: `.get_pk_field(self, model_field)`
+
+Returns the field instance that should be used to represent the pk field.
+
+### get_nested_field
+
+**Signature**: `.get_nested_field(self, model_field)`
+
+Returns the field instance that should be used to represent a related field when `depth` is specified as being non-zero.
+
+### get_related_field
+
+**Signature**: `.get_related_field(self, model_field, to_many=False)`
+
+Returns the field instance that should be used to represent a related field when `depth` is not specified, or when nested representations are being used and the depth reaches zero.
+
+### get_field
+
+**Signature**: `.get_field(self, model_field)`
+
+Returns the field instance that should be used for non-relational, non-pk fields.
+
+### Example:
+
+The following custom model serializer could be used as a base class for model serializers that should always exclude the pk by default.
+
+ class NoPKModelSerializer(serializers.ModelSerializer):
+ def get_pk_field(self, model_field):
+ return None
+
+
+
+[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md
new file mode 100644
index 00000000..a3668e2a
--- /dev/null
+++ b/docs/api-guide/settings.md
@@ -0,0 +1,153 @@
+<a class="github" href="settings.py"></a>
+
+# Settings
+
+> Namespaces are one honking great idea - let's do more of those!
+>
+> &mdash; [The Zen of Python][cite]
+
+Configuration for REST framework is all namespaced inside a single Django setting, named `REST_FRAMEWORK`.
+
+For example your project's `settings.py` file might include something like this:
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework.renderers.YAMLRenderer',
+ )
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework.parsers.YAMLParser',
+ )
+ }
+
+## Accessing settings
+
+If you need to access the values of REST framework's API settings in your project,
+you should use the `api_settings` object. For example.
+
+ from rest_framework.settings import api_settings
+
+ print api_settings.DEFAULT_AUTHENTICATION_CLASSES
+
+The `api_settings` object will check for any user-defined settings, and otherwise fallback to the default values. Any setting that uses string import paths to refer to a class will automatically import and return the referenced class, instead of the string literal.
+
+---
+
+# API Reference
+
+## DEFAULT_RENDERER_CLASSES
+
+A list or tuple of renderer classes, that determines the default set of renderers that may be used when returning a `Response` object.
+
+Default:
+
+ (
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ 'rest_framework.renderers.TemplateHTMLRenderer'
+ )
+
+## DEFAULT_PARSER_CLASSES
+
+A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.DATA` property.
+
+Default:
+
+ (
+ 'rest_framework.parsers.JSONParser',
+ 'rest_framework.parsers.FormParser'
+ )
+
+## DEFAULT_AUTHENTICATION_CLASSES
+
+A list or tuple of authentication classes, that determines the default set of authenticators used when accessing the `request.user` or `request.auth` properties.
+
+Default:
+
+ (
+ 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework.authentication.UserBasicAuthentication'
+ )
+
+## DEFAULT_PERMISSION_CLASSES
+
+A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view.
+
+Default:
+
+ (
+ 'rest_framework.permissions.AllowAny',
+ )
+
+## DEFAULT_THROTTLE_CLASSES
+
+A list or tuple of throttle classes, that determines the default set of throttles checked at the start of a view.
+
+Default: `()`
+
+## DEFAULT_MODEL_SERIALIZER_CLASS
+
+**TODO**
+
+Default: `rest_framework.serializers.ModelSerializer`
+
+## DEFAULT_PAGINATION_SERIALIZER_CLASS
+
+**TODO**
+
+Default: `rest_framework.pagination.PaginationSerializer`
+
+## FORMAT_SUFFIX_KWARG
+
+**TODO**
+
+Default: `'format'`
+
+## UNAUTHENTICATED_USER
+
+The class that should be used to initialize `request.user` for unauthenticated requests.
+
+Default: `django.contrib.auth.models.AnonymousUser`
+
+## UNAUTHENTICATED_TOKEN
+
+The class that should be used to initialize `request.auth` for unauthenticated requests.
+
+Default: `None`
+
+## FORM_METHOD_OVERRIDE
+
+The name of a form field that may be used to override the HTTP method of the form.
+
+If the value of this setting is `None` then form method overloading will be disabled.
+
+Default: `'_method'`
+
+## FORM_CONTENT_OVERRIDE
+
+The name of a form field that may be used to override the content of the form payload. Must be used together with `FORM_CONTENTTYPE_OVERRIDE`.
+
+If either setting is `None` then form content overloading will be disabled.
+
+Default: `'_content'`
+
+## FORM_CONTENTTYPE_OVERRIDE
+
+The name of a form field that may be used to override the content type of the form payload. Must be used together with `FORM_CONTENT_OVERRIDE`.
+
+If either setting is `None` then form content overloading will be disabled.
+
+Default: `'_content_type'`
+
+## URL_ACCEPT_OVERRIDE
+
+The name of a URL parameter that may be used to override the HTTP `Accept` header.
+
+If the value of this setting is `None` then URL accept overloading will be disabled.
+
+Default: `'accept'`
+
+## URL_FORMAT_OVERRIDE
+
+Default: `'format'`
+
+[cite]: http://www.python.org/dev/peps/pep-0020/
diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md
new file mode 100644
index 00000000..401f45ce
--- /dev/null
+++ b/docs/api-guide/status-codes.md
@@ -0,0 +1,95 @@
+<a class="github" href="status.py"></a>
+
+# 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.
+>
+> &mdash; [RFC 2324][rfc2324], Hyper Text Coffee Pot Control Protocol
+
+Using bare status codes in your responses isn't recommended. REST framework includes a set of named constants that you can use to make more code more obvious and readable.
+
+ from rest_framework import status
+
+ def empty_view(self):
+ content = {'please move along': 'nothing to see here'}
+ return Response(content, status=status.HTTP_404_NOT_FOUND)
+
+The full set of HTTP status codes included in the `status` module is listed below.
+
+For more information on proper usage of HTTP status codes see [RFC 2616][rfc2616]
+and [RFC 6585][rfc6585].
+
+## Informational - 1xx
+
+This class of status code indicates a provisional response. There are no 1xx status codes used in REST framework by default.
+
+ HTTP_100_CONTINUE
+ HTTP_101_SWITCHING_PROTOCOLS
+
+## Successful - 2xx
+
+This class of status code indicates that the client's request was successfully received, understood, and accepted.
+
+ HTTP_200_OK
+ HTTP_201_CREATED
+ HTTP_202_ACCEPTED
+ HTTP_203_NON_AUTHORITATIVE_INFORMATION
+ HTTP_204_NO_CONTENT
+ HTTP_205_RESET_CONTENT
+ HTTP_206_PARTIAL_CONTENT
+
+## Redirection - 3xx
+
+This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request.
+
+ HTTP_300_MULTIPLE_CHOICES
+ HTTP_301_MOVED_PERMANENTLY
+ HTTP_302_FOUND
+ HTTP_303_SEE_OTHER
+ HTTP_304_NOT_MODIFIED
+ HTTP_305_USE_PROXY
+ HTTP_306_RESERVED
+ HTTP_307_TEMPORARY_REDIRECT
+
+## Client Error - 4xx
+
+The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.
+
+ HTTP_400_BAD_REQUEST
+ HTTP_401_UNAUTHORIZED
+ HTTP_402_PAYMENT_REQUIRED
+ HTTP_403_FORBIDDEN
+ HTTP_404_NOT_FOUND
+ HTTP_405_METHOD_NOT_ALLOWED
+ HTTP_406_NOT_ACCEPTABLE
+ HTTP_407_PROXY_AUTHENTICATION_REQUIRED
+ HTTP_408_REQUEST_TIMEOUT
+ HTTP_409_CONFLICT
+ HTTP_410_GONE
+ HTTP_411_LENGTH_REQUIRED
+ HTTP_412_PRECONDITION_FAILED
+ HTTP_413_REQUEST_ENTITY_TOO_LARGE
+ HTTP_414_REQUEST_URI_TOO_LONG
+ HTTP_415_UNSUPPORTED_MEDIA_TYPE
+ HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
+ HTTP_417_EXPECTATION_FAILED
+ HTTP_428_PRECONDITION_REQUIRED
+ HTTP_429_TOO_MANY_REQUESTS
+ HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
+
+## Server Error - 5xx
+
+Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.
+
+ HTTP_500_INTERNAL_SERVER_ERROR
+ HTTP_501_NOT_IMPLEMENTED
+ HTTP_502_BAD_GATEWAY
+ HTTP_503_SERVICE_UNAVAILABLE
+ HTTP_504_GATEWAY_TIMEOUT
+ HTTP_505_HTTP_VERSION_NOT_SUPPORTED
+ HTTP_511_NETWORD_AUTHENTICATION_REQUIRED
+
+
+[rfc2324]: http://www.ietf.org/rfc/rfc2324.txt
+[rfc2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+[rfc6585]: http://tools.ietf.org/html/rfc6585
diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md
new file mode 100644
index 00000000..bfda7079
--- /dev/null
+++ b/docs/api-guide/throttling.md
@@ -0,0 +1,157 @@
+<a class="github" href="throttling.py"></a>
+
+# Throttling
+
+> HTTP/1.1 420 Enhance Your Calm
+>
+> [Twitter API rate limiting response][cite]
+
+[cite]: https://dev.twitter.com/docs/error-codes-responses
+
+Throttling is similar to [permissions], in that it determines if a request should be authorized. Throttles indicate a temporary state, and are used to control the rate of requests that clients can make to an API.
+
+As with permissions, multiple throttles may be used. Your API might have a restrictive throttle for unauthenticated requests, and a less restrictive throttle for authenticated requests.
+
+Another scenario where you might want to use multiple throttles would be if you need to impose different constraints on different parts of the API, due to some services being particularly resource-intensive.
+
+Multiple throttles can also be used if you want to impose both burst throttling rates, and sustained throttling rates. For example, you might want to limit a user to a maximum of 60 requests per minute, and 1000 requests per day.
+
+Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed.
+
+## How throttling is determined
+
+As with permissions and authentication, throttling in REST framework is always defined as a list of classes.
+
+Before running the main body of the view each throttle in the list is checked.
+If any throttle check fails an `exceptions.Throttled` exception will be raised, and the main body of the view will not run.
+
+## Setting the throttling policy
+
+The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_CLASSES` and `DEFAULT_THROTTLE_RATES` settings. For example.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_THROTTLE_CLASSES': (
+ 'rest_framework.throttles.AnonThrottle',
+ 'rest_framework.throttles.UserThrottle'
+ ),
+ 'DEFAULT_THROTTLE_RATES': {
+ 'anon': '100/day',
+ 'user': '1000/day'
+ }
+ }
+
+The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period.
+
+You can also set the throttling policy on a per-view basis, using the `APIView` class based views.
+
+ class ExampleView(APIView):
+ throttle_classes = (UserThrottle,)
+
+ def get(self, request, format=None):
+ content = {
+ 'status': 'request was permitted'
+ }
+ return Response(content)
+
+Or, if you're using the `@api_view` decorator with function based views.
+
+ @api_view('GET')
+ @throttle_classes(UserThrottle)
+ def example_view(request, format=None):
+ content = {
+ 'status': 'request was permitted'
+ }
+ return Response(content)
+
+---
+
+# API Reference
+
+## AnonRateThrottle
+
+The `AnonThrottle` will only ever throttle unauthenticated users. The IP address of the incoming request is used to generate a unique key to throttle against.
+
+The allowed request rate is determined from one of the following (in order of preference).
+
+* The `rate` property on the class, which may be provided by overriding `AnonThrottle` and setting the property.
+* The `DEFAULT_THROTTLE_RATES['anon']` setting.
+
+`AnonThrottle` is suitable if you want to restrict the rate of requests from unknown sources.
+
+## UserRateThrottle
+
+The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against.
+
+The allowed request rate is determined from one of the following (in order of preference).
+
+* The `rate` property on the class, which may be provided by overriding `UserThrottle` and setting the property.
+* The `DEFAULT_THROTTLE_RATES['user']` setting.
+
+An API may have multiple `UserRateThrottles` in place at the same time. To do so, override `UserRateThrottle` and set a unique "scope" for each class.
+
+For example, multiple user throttle rates could be implemented by using the following classes...
+
+ class BurstRateThrottle(UserRateThrottle):
+ scope = 'burst'
+
+ class SustainedRateThrottle(UserRateThrottle):
+ scope = 'sustained'
+
+...and the following settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_THROTTLE_CLASSES': (
+ 'example.throttles.BurstRateThrottle',
+ 'example.throttles.SustainedRateThrottle'
+ ),
+ 'DEFAULT_THROTTLE_RATES': {
+ 'burst': '60/min',
+ 'sustained': '1000/day'
+ }
+ }
+
+`UserThrottle` is suitable if you want simple global rate restrictions per-user.
+
+## ScopedRateThrottle
+
+The `ScopedThrottle` class can be used to restrict access to specific parts of the API. This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property. The unique throttle key will then be formed by concatenating the "scope" of the request with the unique user id or IP address.
+
+The allowed request rate is determined by the `DEFAULT_THROTTLE_RATES` setting using a key from the request "scope".
+
+For example, given the following views...
+
+ class ContactListView(APIView):
+ throttle_scope = 'contacts'
+ ...
+
+ class ContactDetailView(ApiView):
+ throttle_scope = 'contacts'
+ ...
+
+ class UploadView(APIView):
+ throttle_scope = 'uploads'
+ ...
+
+...and the following settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_THROTTLE_CLASSES': (
+ 'rest_framework.throttles.ScopedRateThrottle'
+ ),
+ 'DEFAULT_THROTTLE_RATES': {
+ 'contacts': '1000/day',
+ 'uploads': '20/day'
+ }
+ }
+
+User requests to either `ContactListView` or `ContactDetailView` would be restricted to a total of 1000 requests per-day. User requests to `UploadView` would be restricted to 20 requests per day.
+
+---
+
+# Custom throttles
+
+To create a custom throttle, override `BaseThrottle` and implement `.allow_request(request, view)`. The method should return `True` if the request should be allowed, and `False` otherwise.
+
+Optionally you may also override the `.wait()` method. If implemented, `.wait()` should return a recommended number of seconds to wait before attempting the next request, or `None`. The `.wait()` method will only be called if `.allow_request()` has previously returned `False`.
+
+[permissions]: permissions.md
diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md
new file mode 100644
index 00000000..5b072827
--- /dev/null
+++ b/docs/api-guide/views.md
@@ -0,0 +1,168 @@
+<a class="github" href="decorators.py"></a> <a class="github" href="views.py"></a>
+
+# Class Based Views
+
+> Django's class based views are a welcome departure from the old-style views.
+>
+> &mdash; [Reinout van Rees][cite]
+
+REST framework provides an `APIView` class, which subclasses Django's `View` class.
+
+`APIView` classes are different from regular `View` classes in the following ways:
+
+* Requests passed to the handler methods will be REST framework's `Request` instances, not Django's `HttpRequest` instances.
+* Handler methods may return REST framework's `Response`, instead of Django's `HttpResponse`. The view will manage content negotiation and setting the correct renderer on the response.
+* Any `APIException` exceptions will be caught and mediated into appropriate responses.
+* Incoming requests will be authenticated and appropriate permission and/or throttle checks will be run before dispatching the request to the handler method.
+
+Using the `APIView` class is pretty much the same as using a regular `View` class, as usual, the incoming request is dispatched to an appropriate handler method such as `.get()` or `.post()`. Additionally, a number of attributes may be set on the class that control various aspects of the API policy.
+
+For example:
+
+ class ListUsers(APIView):
+ """
+ View to list all users in the system.
+
+ * Requires token authentication.
+ * Only admin users are able to access this view.
+ """
+ authentication_classes = (authentication.TokenAuthentication,)
+ permission_classes = (permissions.IsAdminUser,)
+
+ def get(self, request, format=None):
+ """
+ Return a list of all users.
+ """
+ usernames = [user.username for user in User.objects.all()]
+ return Response(usernames)
+
+## API policy attributes
+
+The following attributes control the pluggable aspects of API views.
+
+### .renderer_classes
+
+### .parser_classes
+
+### .authentication_classes
+
+### .throttle_classes
+
+### .permission_classes
+
+### .content_negotiation_class
+
+## API policy instantiation methods
+
+The following methods are used by REST framework to instantiate the various pluggable API policies. You won't typically need to override these methods.
+
+### .get_renderers(self)
+
+### .get_parsers(self)
+
+### .get_authenticators(self)
+
+### .get_throttles(self)
+
+### .get_permissions(self)
+
+### .get_content_negotiator(self)
+
+## API policy implementation methods
+
+The following methods are called before dispatching to the handler method.
+
+### .check_permissions(...)
+
+### .check_throttles(...)
+
+### .perform_content_negotiation(...)
+
+## Dispatch methods
+
+The following methods are called directly by the view's `.dispatch()` method.
+These perform any actions that need to occur before or after calling the handler methods such as `.get()`, `.post()`, `put()` and `.delete()`.
+
+### .initial(self, request, \*args, **kwargs)
+
+Performs any actions that need to occur before the handler method gets called.
+This method is used to enforce permissions and throttling, and perform content negotiation.
+
+You won't typically need to override this method.
+
+### .handle_exception(self, exc)
+
+Any exception thrown by the handler method will be passed to this method, which either returns a `Response` instance, or re-raises the exception.
+
+The default implementation handles any subclass of `rest_framework.exceptions.APIException`, as well as Django's `Http404` and `PermissionDenied` exceptions, and returns an appropriate error response.
+
+If you need to customize the error responses your API returns you should subclass this method.
+
+### .initialize_request(self, request, \*args, **kwargs)
+
+Ensures that the request object that is passed to the handler method is an instance of `Request`, rather than the usual Django `HttpRequest`.
+
+You won't typically need to override this method.
+
+### .finalize_response(self, request, response, \*args, **kwargs)
+
+Ensures that any `Response` object returned from the handler method will be rendered into the correct content type, as determined by the content negotation.
+
+You won't typically need to override this method.
+
+---
+
+# Function Based Views
+
+> Saying [that Class based views] is always the superior solution is a mistake.
+>
+> &mdash; [Nick Coghlan][cite2]
+
+REST framework also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of `Request` (rather than the usual Django `HttpRequest`) and allows them to return a `Response` (instead of a Django `HttpResponse`), and allow you to configure how the request is processed.
+
+## @api_view()
+
+**Signature:** `@api_view(http_method_names)`
+
+The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data:
+
+ from rest_framework.decorators import api_view
+
+ @api_view(['GET'])
+ def hello_world(request):
+ return Response({"message": "Hello, world!"})
+
+
+This view will use the default renderers, parsers, authentication classes etc specified in the [settings](settings).
+
+## API policy decorators
+
+To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `@api_view` decorator. For example, to create a view that uses a [throttle](throttling) to ensure it can only be called once per day by a particular user, use the `@throttle_classes` decorator, passing a list of throttle classes:
+
+ from rest_framework.decorators import api_view, throttle_classes
+ from rest_framework.throttling import UserRateThrottle
+
+ class OncePerDayUserThrottle(UserRateThrottle):
+ rate = '1/day'
+
+ @api_view(['GET'])
+ @throttle_classes([OncePerDayUserThrottle])
+ def view(request):
+ return Response({"message": "Hello for today! See you tomorrow!"})
+
+These decorators correspond to the attributes set on `APIView` subclasses, described above.
+
+The available decorators are:
+
+* `@renderer_classes(...)`
+* `@parser_classes(...)`
+* `@authentication_classes(...)`
+* `@throttle_classes(...)`
+* `@permission_classes(...)`
+
+Each of these decorators takes a single argument which must be a list or tuple of classes.
+
+[cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html
+[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html
+[settings]: api-guide/settings.md
+[throttling]: api-guide/throttling.md
diff --git a/docs/check_sphinx.py b/docs/check_sphinx.py
deleted file mode 100644
index feb04abd..00000000
--- 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 16388814..00000000
--- 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
-# "<project> v<release> 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 <link> 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 d8e6e742..00000000
--- a/docs/contents.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Documentation
-=============
-
-.. toctree::
- :maxdepth: 2
-
- howto
- library
- examples
-
diff --git a/docs/css/bootstrap-responsive.css b/docs/css/bootstrap-responsive.css
new file mode 100755
index 00000000..a8caf451
--- /dev/null
+++ b/docs/css/bootstrap-responsive.css
@@ -0,0 +1,1058 @@
+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+.visible-phone {
+ display: none !important;
+}
+
+.visible-tablet {
+ display: none !important;
+}
+
+.hidden-desktop {
+ display: none !important;
+}
+
+.visible-desktop {
+ display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important ;
+ }
+ .visible-tablet {
+ display: inherit !important;
+ }
+ .hidden-tablet {
+ display: none !important;
+ }
+}
+
+@media (max-width: 767px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important;
+ }
+ .visible-phone {
+ display: inherit !important;
+ }
+ .hidden-phone {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .row {
+ margin-left: -30px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 30px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 1170px;
+ }
+ .span12 {
+ width: 1170px;
+ }
+ .span11 {
+ width: 1070px;
+ }
+ .span10 {
+ width: 970px;
+ }
+ .span9 {
+ width: 870px;
+ }
+ .span8 {
+ width: 770px;
+ }
+ .span7 {
+ width: 670px;
+ }
+ .span6 {
+ width: 570px;
+ }
+ .span5 {
+ width: 470px;
+ }
+ .span4 {
+ width: 370px;
+ }
+ .span3 {
+ width: 270px;
+ }
+ .span2 {
+ width: 170px;
+ }
+ .span1 {
+ width: 70px;
+ }
+ .offset12 {
+ margin-left: 1230px;
+ }
+ .offset11 {
+ margin-left: 1130px;
+ }
+ .offset10 {
+ margin-left: 1030px;
+ }
+ .offset9 {
+ margin-left: 930px;
+ }
+ .offset8 {
+ margin-left: 830px;
+ }
+ .offset7 {
+ margin-left: 730px;
+ }
+ .offset6 {
+ margin-left: 630px;
+ }
+ .offset5 {
+ margin-left: 530px;
+ }
+ .offset4 {
+ margin-left: 430px;
+ }
+ .offset3 {
+ margin-left: 330px;
+ }
+ .offset2 {
+ margin-left: 230px;
+ }
+ .offset1 {
+ margin-left: 130px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.564102564102564%;
+ *margin-left: 2.5109110747408616%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.45299145299145%;
+ *width: 91.39979996362975%;
+ }
+ .row-fluid .span10 {
+ width: 82.90598290598291%;
+ *width: 82.8527914166212%;
+ }
+ .row-fluid .span9 {
+ width: 74.35897435897436%;
+ *width: 74.30578286961266%;
+ }
+ .row-fluid .span8 {
+ width: 65.81196581196582%;
+ *width: 65.75877432260411%;
+ }
+ .row-fluid .span7 {
+ width: 57.26495726495726%;
+ *width: 57.21176577559556%;
+ }
+ .row-fluid .span6 {
+ width: 48.717948717948715%;
+ *width: 48.664757228587014%;
+ }
+ .row-fluid .span5 {
+ width: 40.17094017094017%;
+ *width: 40.11774868157847%;
+ }
+ .row-fluid .span4 {
+ width: 31.623931623931625%;
+ *width: 31.570740134569924%;
+ }
+ .row-fluid .span3 {
+ width: 23.076923076923077%;
+ *width: 23.023731587561375%;
+ }
+ .row-fluid .span2 {
+ width: 14.52991452991453%;
+ *width: 14.476723040552828%;
+ }
+ .row-fluid .span1 {
+ width: 5.982905982905983%;
+ *width: 5.929714493544281%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.12820512820512%;
+ *margin-left: 105.02182214948171%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.56410256410257%;
+ *margin-left: 102.45771958537915%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.58119658119658%;
+ *margin-left: 96.47481360247316%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.01709401709402%;
+ *margin-left: 93.91071103837061%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.03418803418803%;
+ *margin-left: 87.92780505546462%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.47008547008548%;
+ *margin-left: 85.36370249136206%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.48717948717949%;
+ *margin-left: 79.38079650845607%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 76.92307692307693%;
+ *margin-left: 76.81669394435352%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 70.94017094017094%;
+ *margin-left: 70.83378796144753%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.37606837606839%;
+ *margin-left: 68.26968539734497%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.393162393162385%;
+ *margin-left: 62.28677941443899%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.82905982905982%;
+ *margin-left: 59.72267685033642%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 53.84615384615384%;
+ *margin-left: 53.739770867430444%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.28205128205128%;
+ *margin-left: 51.175668303327875%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.299145299145295%;
+ *margin-left: 45.1927623204219%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.73504273504273%;
+ *margin-left: 42.62865975631933%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 36.75213675213675%;
+ *margin-left: 36.645753773413354%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.18803418803419%;
+ *margin-left: 34.081651209310785%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.205128205128204%;
+ *margin-left: 28.0987452264048%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.641025641025642%;
+ *margin-left: 25.53464266230224%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.65811965811966%;
+ *margin-left: 19.551736679396257%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.094017094017094%;
+ *margin-left: 16.98763411529369%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.11111111111111%;
+ *margin-left: 11.004728132387708%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.547008547008547%;
+ *margin-left: 8.440625568285142%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 30px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 1156px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 1056px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 956px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 856px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 756px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 656px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 556px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 456px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 356px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 256px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 156px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 56px;
+ }
+ .thumbnails {
+ margin-left: -30px;
+ }
+ .thumbnails > li {
+ margin-left: 30px;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .row {
+ margin-left: -20px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 724px;
+ }
+ .span12 {
+ width: 724px;
+ }
+ .span11 {
+ width: 662px;
+ }
+ .span10 {
+ width: 600px;
+ }
+ .span9 {
+ width: 538px;
+ }
+ .span8 {
+ width: 476px;
+ }
+ .span7 {
+ width: 414px;
+ }
+ .span6 {
+ width: 352px;
+ }
+ .span5 {
+ width: 290px;
+ }
+ .span4 {
+ width: 228px;
+ }
+ .span3 {
+ width: 166px;
+ }
+ .span2 {
+ width: 104px;
+ }
+ .span1 {
+ width: 42px;
+ }
+ .offset12 {
+ margin-left: 764px;
+ }
+ .offset11 {
+ margin-left: 702px;
+ }
+ .offset10 {
+ margin-left: 640px;
+ }
+ .offset9 {
+ margin-left: 578px;
+ }
+ .offset8 {
+ margin-left: 516px;
+ }
+ .offset7 {
+ margin-left: 454px;
+ }
+ .offset6 {
+ margin-left: 392px;
+ }
+ .offset5 {
+ margin-left: 330px;
+ }
+ .offset4 {
+ margin-left: 268px;
+ }
+ .offset3 {
+ margin-left: 206px;
+ }
+ .offset2 {
+ margin-left: 144px;
+ }
+ .offset1 {
+ margin-left: 82px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.7624309392265194%;
+ *margin-left: 2.709239449864817%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.43646408839778%;
+ *width: 91.38327259903608%;
+ }
+ .row-fluid .span10 {
+ width: 82.87292817679558%;
+ *width: 82.81973668743387%;
+ }
+ .row-fluid .span9 {
+ width: 74.30939226519337%;
+ *width: 74.25620077583166%;
+ }
+ .row-fluid .span8 {
+ width: 65.74585635359117%;
+ *width: 65.69266486422946%;
+ }
+ .row-fluid .span7 {
+ width: 57.18232044198895%;
+ *width: 57.12912895262725%;
+ }
+ .row-fluid .span6 {
+ width: 48.61878453038674%;
+ *width: 48.56559304102504%;
+ }
+ .row-fluid .span5 {
+ width: 40.05524861878453%;
+ *width: 40.00205712942283%;
+ }
+ .row-fluid .span4 {
+ width: 31.491712707182323%;
+ *width: 31.43852121782062%;
+ }
+ .row-fluid .span3 {
+ width: 22.92817679558011%;
+ *width: 22.87498530621841%;
+ }
+ .row-fluid .span2 {
+ width: 14.3646408839779%;
+ *width: 14.311449394616199%;
+ }
+ .row-fluid .span1 {
+ width: 5.801104972375691%;
+ *width: 5.747913483013988%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.52486187845304%;
+ *margin-left: 105.41847889972962%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.76243093922652%;
+ *margin-left: 102.6560479605031%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.96132596685082%;
+ *margin-left: 96.8549429881274%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.1988950276243%;
+ *margin-left: 94.09251204890089%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.39779005524862%;
+ *margin-left: 88.2914070765252%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.6353591160221%;
+ *margin-left: 85.52897613729868%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.8342541436464%;
+ *margin-left: 79.72787116492299%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 77.07182320441989%;
+ *margin-left: 76.96544022569647%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 71.2707182320442%;
+ *margin-left: 71.16433525332079%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.50828729281768%;
+ *margin-left: 68.40190431409427%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.70718232044199%;
+ *margin-left: 62.600799341718584%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.94475138121547%;
+ *margin-left: 59.838368402492065%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 54.14364640883978%;
+ *margin-left: 54.037263430116376%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.38121546961326%;
+ *margin-left: 51.27483249088986%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.58011049723757%;
+ *margin-left: 45.47372751851417%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.81767955801105%;
+ *margin-left: 42.71129657928765%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 37.01657458563536%;
+ *margin-left: 36.91019160691196%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.25414364640884%;
+ *margin-left: 34.14776066768544%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.45303867403315%;
+ *margin-left: 28.346655695309746%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.69060773480663%;
+ *margin-left: 25.584224756083227%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.88950276243094%;
+ *margin-left: 19.783119783707537%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.12707182320442%;
+ *margin-left: 17.02068884448102%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.32596685082873%;
+ *margin-left: 11.219583872105325%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.56353591160221%;
+ *margin-left: 8.457152932878806%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 710px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 648px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 586px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 524px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 462px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 400px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 338px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 276px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 214px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 152px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 90px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 28px;
+ }
+}
+
+@media (max-width: 767px) {
+ body {
+ padding-right: 20px;
+ padding-left: 20px;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom,
+ .navbar-static-top {
+ margin-right: -20px;
+ margin-left: -20px;
+ }
+ .container-fluid {
+ padding: 0;
+ }
+ .dl-horizontal dt {
+ float: none;
+ width: auto;
+ clear: none;
+ text-align: left;
+ }
+ .dl-horizontal dd {
+ margin-left: 0;
+ }
+ .container {
+ width: auto;
+ }
+ .row-fluid {
+ width: 100%;
+ }
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0;
+ }
+ [class*="span"],
+ .row-fluid [class*="span"] {
+ display: block;
+ float: none;
+ width: 100%;
+ margin-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block;
+ width: auto;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 0;
+ }
+ .modal {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ left: 20px;
+ width: auto;
+ margin: 0;
+ }
+ .modal.fade.in {
+ top: auto;
+ }
+}
+
+@media (max-width: 480px) {
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0);
+ }
+ .page-header h1 small {
+ display: block;
+ line-height: 20px;
+ }
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+ .form-horizontal .control-label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ .form-horizontal .controls {
+ margin-left: 0;
+ }
+ .form-horizontal .control-list {
+ padding-top: 0;
+ }
+ .form-horizontal .form-actions {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+ .modal {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+ .carousel-caption {
+ position: static;
+ }
+}
+
+@media (max-width: 979px) {
+ body {
+ padding-top: 0;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: 20px;
+ }
+ .navbar-fixed-bottom {
+ margin-top: 20px;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ .navbar .brand {
+ padding-right: 10px;
+ padding-left: 10px;
+ margin: 0 0 0 -5px;
+ }
+ .nav-collapse {
+ clear: both;
+ }
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 10px;
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: #777777;
+ text-shadow: none;
+ }
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: #777777;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ }
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .dropdown-menu a:hover {
+ background-color: #f2f2f2;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+ background-color: #111111;
+ }
+ .nav-collapse.in .btn-group {
+ padding: 0;
+ margin-top: 5px;
+ }
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ display: block;
+ float: none;
+ max-width: none;
+ padding: 0;
+ margin: 0 15px;
+ background-color: transparent;
+ border: none;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+ .nav-collapse .dropdown-menu:before,
+ .nav-collapse .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .dropdown-menu .divider {
+ display: none;
+ }
+ .nav-collapse .nav > li > .dropdown-menu:before,
+ .nav-collapse .nav > li > .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .navbar-form,
+ .nav-collapse .navbar-search {
+ float: none;
+ padding: 10px 15px;
+ margin: 10px 0;
+ border-top: 1px solid #f2f2f2;
+ border-bottom: 1px solid #f2f2f2;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ }
+ .navbar-inverse .nav-collapse .navbar-form,
+ .navbar-inverse .nav-collapse .navbar-search {
+ border-top-color: #111111;
+ border-bottom-color: #111111;
+ }
+ .navbar .nav-collapse .nav.pull-right {
+ float: none;
+ margin-left: 0;
+ }
+ .nav-collapse,
+ .nav-collapse.collapse {
+ height: 0;
+ overflow: hidden;
+ }
+ .navbar .btn-navbar {
+ display: block;
+ }
+ .navbar-static .navbar-inner {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+}
+
+@media (min-width: 980px) {
+ .nav-collapse.collapse {
+ height: auto !important;
+ overflow: visible !important;
+ }
+} \ No newline at end of file
diff --git a/docs/css/bootstrap.css b/docs/css/bootstrap.css
new file mode 100755
index 00000000..53df6859
--- /dev/null
+++ b/docs/css/bootstrap.css
@@ -0,0 +1,5774 @@
+/*!
+ * Bootstrap v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+audio:not([controls]) {
+ display: none;
+}
+
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+ outline: 0;
+}
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+img {
+ width: auto\9;
+ height: auto;
+ max-width: 100%;
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img {
+ max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+
+button,
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+
+input[type="search"] {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+ background-color: #ffffff;
+}
+
+a {
+ color: #0088cc;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #005580;
+ text-decoration: underline;
+}
+
+.img-rounded {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.img-polaroid {
+ padding: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.img-circle {
+ -webkit-border-radius: 500px;
+ -moz-border-radius: 500px;
+ border-radius: 500px;
+}
+
+.row {
+ margin-left: -20px;
+ *zoom: 1;
+}
+
+.row:before,
+.row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row:after {
+ clear: both;
+}
+
+[class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+}
+
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.span12 {
+ width: 940px;
+}
+
+.span11 {
+ width: 860px;
+}
+
+.span10 {
+ width: 780px;
+}
+
+.span9 {
+ width: 700px;
+}
+
+.span8 {
+ width: 620px;
+}
+
+.span7 {
+ width: 540px;
+}
+
+.span6 {
+ width: 460px;
+}
+
+.span5 {
+ width: 380px;
+}
+
+.span4 {
+ width: 300px;
+}
+
+.span3 {
+ width: 220px;
+}
+
+.span2 {
+ width: 140px;
+}
+
+.span1 {
+ width: 60px;
+}
+
+.offset12 {
+ margin-left: 980px;
+}
+
+.offset11 {
+ margin-left: 900px;
+}
+
+.offset10 {
+ margin-left: 820px;
+}
+
+.offset9 {
+ margin-left: 740px;
+}
+
+.offset8 {
+ margin-left: 660px;
+}
+
+.offset7 {
+ margin-left: 580px;
+}
+
+.offset6 {
+ margin-left: 500px;
+}
+
+.offset5 {
+ margin-left: 420px;
+}
+
+.offset4 {
+ margin-left: 340px;
+}
+
+.offset3 {
+ margin-left: 260px;
+}
+
+.offset2 {
+ margin-left: 180px;
+}
+
+.offset1 {
+ margin-left: 100px;
+}
+
+.row-fluid {
+ width: 100%;
+ *zoom: 1;
+}
+
+.row-fluid:before,
+.row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row-fluid:after {
+ clear: both;
+}
+
+.row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.127659574468085%;
+ *margin-left: 2.074468085106383%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+}
+
+.row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+}
+
+.row-fluid .span11 {
+ width: 91.48936170212765%;
+ *width: 91.43617021276594%;
+}
+
+.row-fluid .span10 {
+ width: 82.97872340425532%;
+ *width: 82.92553191489361%;
+}
+
+.row-fluid .span9 {
+ width: 74.46808510638297%;
+ *width: 74.41489361702126%;
+}
+
+.row-fluid .span8 {
+ width: 65.95744680851064%;
+ *width: 65.90425531914893%;
+}
+
+.row-fluid .span7 {
+ width: 57.44680851063829%;
+ *width: 57.39361702127659%;
+}
+
+.row-fluid .span6 {
+ width: 48.93617021276595%;
+ *width: 48.88297872340425%;
+}
+
+.row-fluid .span5 {
+ width: 40.42553191489362%;
+ *width: 40.37234042553192%;
+}
+
+.row-fluid .span4 {
+ width: 31.914893617021278%;
+ *width: 31.861702127659576%;
+}
+
+.row-fluid .span3 {
+ width: 23.404255319148934%;
+ *width: 23.351063829787233%;
+}
+
+.row-fluid .span2 {
+ width: 14.893617021276595%;
+ *width: 14.840425531914894%;
+}
+
+.row-fluid .span1 {
+ width: 6.382978723404255%;
+ *width: 6.329787234042553%;
+}
+
+.row-fluid .offset12 {
+ margin-left: 104.25531914893617%;
+ *margin-left: 104.14893617021275%;
+}
+
+.row-fluid .offset12:first-child {
+ margin-left: 102.12765957446808%;
+ *margin-left: 102.02127659574467%;
+}
+
+.row-fluid .offset11 {
+ margin-left: 95.74468085106382%;
+ *margin-left: 95.6382978723404%;
+}
+
+.row-fluid .offset11:first-child {
+ margin-left: 93.61702127659574%;
+ *margin-left: 93.51063829787232%;
+}
+
+.row-fluid .offset10 {
+ margin-left: 87.23404255319149%;
+ *margin-left: 87.12765957446807%;
+}
+
+.row-fluid .offset10:first-child {
+ margin-left: 85.1063829787234%;
+ *margin-left: 84.99999999999999%;
+}
+
+.row-fluid .offset9 {
+ margin-left: 78.72340425531914%;
+ *margin-left: 78.61702127659572%;
+}
+
+.row-fluid .offset9:first-child {
+ margin-left: 76.59574468085106%;
+ *margin-left: 76.48936170212764%;
+}
+
+.row-fluid .offset8 {
+ margin-left: 70.2127659574468%;
+ *margin-left: 70.10638297872339%;
+}
+
+.row-fluid .offset8:first-child {
+ margin-left: 68.08510638297872%;
+ *margin-left: 67.9787234042553%;
+}
+
+.row-fluid .offset7 {
+ margin-left: 61.70212765957446%;
+ *margin-left: 61.59574468085106%;
+}
+
+.row-fluid .offset7:first-child {
+ margin-left: 59.574468085106375%;
+ *margin-left: 59.46808510638297%;
+}
+
+.row-fluid .offset6 {
+ margin-left: 53.191489361702125%;
+ *margin-left: 53.085106382978715%;
+}
+
+.row-fluid .offset6:first-child {
+ margin-left: 51.063829787234035%;
+ *margin-left: 50.95744680851063%;
+}
+
+.row-fluid .offset5 {
+ margin-left: 44.68085106382979%;
+ *margin-left: 44.57446808510638%;
+}
+
+.row-fluid .offset5:first-child {
+ margin-left: 42.5531914893617%;
+ *margin-left: 42.4468085106383%;
+}
+
+.row-fluid .offset4 {
+ margin-left: 36.170212765957444%;
+ *margin-left: 36.06382978723405%;
+}
+
+.row-fluid .offset4:first-child {
+ margin-left: 34.04255319148936%;
+ *margin-left: 33.93617021276596%;
+}
+
+.row-fluid .offset3 {
+ margin-left: 27.659574468085104%;
+ *margin-left: 27.5531914893617%;
+}
+
+.row-fluid .offset3:first-child {
+ margin-left: 25.53191489361702%;
+ *margin-left: 25.425531914893618%;
+}
+
+.row-fluid .offset2 {
+ margin-left: 19.148936170212764%;
+ *margin-left: 19.04255319148936%;
+}
+
+.row-fluid .offset2:first-child {
+ margin-left: 17.02127659574468%;
+ *margin-left: 16.914893617021278%;
+}
+
+.row-fluid .offset1 {
+ margin-left: 10.638297872340425%;
+ *margin-left: 10.53191489361702%;
+}
+
+.row-fluid .offset1:first-child {
+ margin-left: 8.51063829787234%;
+ *margin-left: 8.404255319148938%;
+}
+
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+ display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
+
+.container {
+ margin-right: auto;
+ margin-left: auto;
+ *zoom: 1;
+}
+
+.container:before,
+.container:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container:after {
+ clear: both;
+}
+
+.container-fluid {
+ padding-right: 20px;
+ padding-left: 20px;
+ *zoom: 1;
+}
+
+.container-fluid:before,
+.container-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container-fluid:after {
+ clear: both;
+}
+
+p {
+ margin: 0 0 10px;
+}
+
+.lead {
+ margin-bottom: 20px;
+ font-size: 21px;
+ font-weight: 200;
+ line-height: 30px;
+}
+
+small {
+ font-size: 85%;
+}
+
+strong {
+ font-weight: bold;
+}
+
+em {
+ font-style: italic;
+}
+
+cite {
+ font-style: normal;
+}
+
+.muted {
+ color: #999999;
+}
+
+.text-warning {
+ color: #c09853;
+}
+
+.text-error {
+ color: #b94a48;
+}
+
+.text-info {
+ color: #3a87ad;
+}
+
+.text-success {
+ color: #468847;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 10px 0;
+ font-family: inherit;
+ font-weight: bold;
+ line-height: 1;
+ color: inherit;
+ text-rendering: optimizelegibility;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+ font-weight: normal;
+ line-height: 1;
+ color: #999999;
+}
+
+h1 {
+ font-size: 36px;
+ line-height: 40px;
+}
+
+h2 {
+ font-size: 30px;
+ line-height: 40px;
+}
+
+h3 {
+ font-size: 24px;
+ line-height: 40px;
+}
+
+h4 {
+ font-size: 18px;
+ line-height: 20px;
+}
+
+h5 {
+ font-size: 14px;
+ line-height: 20px;
+}
+
+h6 {
+ font-size: 12px;
+ line-height: 20px;
+}
+
+h1 small {
+ font-size: 24px;
+}
+
+h2 small {
+ font-size: 18px;
+}
+
+h3 small {
+ font-size: 14px;
+}
+
+h4 small {
+ font-size: 14px;
+}
+
+.page-header {
+ padding-bottom: 9px;
+ margin: 20px 0 30px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+ padding: 0;
+ margin: 0 0 10px 25px;
+}
+
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+
+li {
+ line-height: 20px;
+}
+
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+
+dl {
+ margin-bottom: 20px;
+}
+
+dt,
+dd {
+ line-height: 20px;
+}
+
+dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-left: 10px;
+}
+
+.dl-horizontal {
+ *zoom: 1;
+}
+
+.dl-horizontal:before,
+.dl-horizontal:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.dl-horizontal:after {
+ clear: both;
+}
+
+.dl-horizontal dt {
+ float: left;
+ width: 160px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dl-horizontal dd {
+ margin-left: 180px;
+}
+
+hr {
+ margin: 20px 0;
+ border: 0;
+ border-top: 1px solid #eeeeee;
+ border-bottom: 1px solid #ffffff;
+}
+
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 20px;
+ border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+ margin-bottom: 0;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 25px;
+}
+
+blockquote small {
+ display: block;
+ line-height: 20px;
+ color: #999999;
+}
+
+blockquote small:before {
+ content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+ float: right;
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #eeeeee;
+ border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+ text-align: right;
+}
+
+blockquote.pull-right small:before {
+ content: '';
+}
+
+blockquote.pull-right small:after {
+ content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+address {
+ display: block;
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 20px;
+}
+
+code,
+pre {
+ padding: 0 3px 2px;
+ font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+ font-size: 12px;
+ color: #333333;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+code {
+ padding: 2px 4px;
+ color: #d14;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 20px;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+pre.prettyprint {
+ margin-bottom: 20px;
+}
+
+pre code {
+ padding: 0;
+ color: inherit;
+ background-color: transparent;
+ border: 0;
+}
+
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+
+form {
+ margin: 0 0 20px;
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: 40px;
+ color: #333333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+legend small {
+ font-size: 15px;
+ color: #999999;
+}
+
+label,
+input,
+button,
+select,
+textarea {
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+}
+
+input,
+button,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+label {
+ display: block;
+ margin-bottom: 5px;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ display: inline-block;
+ height: 20px;
+ padding: 4px 6px;
+ margin-bottom: 9px;
+ font-size: 14px;
+ line-height: 20px;
+ color: #555555;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+input,
+textarea,
+.uneditable-input {
+ width: 206px;
+}
+
+textarea {
+ height: auto;
+}
+
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ outline: thin dotted \9;
+ /* IE6-9 */
+
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ *margin-top: 0;
+ line-height: normal;
+ cursor: pointer;
+}
+
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+ width: auto;
+}
+
+select,
+input[type="file"] {
+ height: 30px;
+ /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+ *margin-top: 4px;
+ /* For IE7, add top margin to align select with labels */
+
+ line-height: 30px;
+}
+
+select {
+ width: 220px;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+}
+
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.uneditable-input,
+.uneditable-textarea {
+ color: #999999;
+ cursor: not-allowed;
+ background-color: #fcfcfc;
+ border-color: #cccccc;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+}
+
+.uneditable-input {
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+ color: #999999;
+}
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+ color: #999999;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+ color: #999999;
+}
+
+.radio,
+.checkbox {
+ min-height: 18px;
+ padding-left: 18px;
+}
+
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -18px;
+}
+
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px;
+}
+
+.radio.inline,
+.checkbox.inline {
+ display: inline-block;
+ padding-top: 5px;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+ margin-left: 10px;
+}
+
+.input-mini {
+ width: 60px;
+}
+
+.input-small {
+ width: 90px;
+}
+
+.input-medium {
+ width: 150px;
+}
+
+.input-large {
+ width: 210px;
+}
+
+.input-xlarge {
+ width: 270px;
+}
+
+.input-xxlarge {
+ width: 530px;
+}
+
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+ float: none;
+ margin-left: 0;
+}
+
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+ display: inline-block;
+}
+
+input,
+textarea,
+.uneditable-input {
+ margin-left: 0;
+}
+
+.controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+}
+
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+ width: 926px;
+}
+
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+ width: 846px;
+}
+
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+ width: 766px;
+}
+
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+ width: 686px;
+}
+
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+ width: 606px;
+}
+
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+ width: 526px;
+}
+
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+ width: 446px;
+}
+
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+ width: 366px;
+}
+
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+ width: 286px;
+}
+
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+ width: 206px;
+}
+
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+ width: 126px;
+}
+
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+ width: 46px;
+}
+
+.controls-row {
+ *zoom: 1;
+}
+
+.controls-row:before,
+.controls-row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.controls-row:after {
+ clear: both;
+}
+
+.controls-row [class*="span"] {
+ float: left;
+}
+
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: not-allowed;
+ background-color: #eeeeee;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+ background-color: transparent;
+}
+
+.control-group.warning > label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+ color: #c09853;
+}
+
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ color: #c09853;
+}
+
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ border-color: #c09853;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+ border-color: #a47e3c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #c09853;
+}
+
+.control-group.error > label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+ color: #b94a48;
+}
+
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ color: #b94a48;
+}
+
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ border-color: #b94a48;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+ border-color: #953b39;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #b94a48;
+}
+
+.control-group.success > label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+ color: #468847;
+}
+
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ color: #468847;
+}
+
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ border-color: #468847;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+ border-color: #356635;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #468847;
+}
+
+.control-group.info > label,
+.control-group.info .help-block,
+.control-group.info .help-inline {
+ color: #3a87ad;
+}
+
+.control-group.info .checkbox,
+.control-group.info .radio,
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+ color: #3a87ad;
+}
+
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+ border-color: #3a87ad;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.info input:focus,
+.control-group.info select:focus,
+.control-group.info textarea:focus {
+ border-color: #2d6987;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+}
+
+.control-group.info .input-prepend .add-on,
+.control-group.info .input-append .add-on {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #3a87ad;
+}
+
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+}
+
+input:focus:required:invalid:focus,
+textarea:focus:required:invalid:focus,
+select:focus:required:invalid:focus {
+ border-color: #e9322d;
+ -webkit-box-shadow: 0 0 6px #f8b9b7;
+ -moz-box-shadow: 0 0 6px #f8b9b7;
+ box-shadow: 0 0 6px #f8b9b7;
+}
+
+.form-actions {
+ padding: 19px 20px 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #e5e5e5;
+ *zoom: 1;
+}
+
+.form-actions:before,
+.form-actions:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-actions:after {
+ clear: both;
+}
+
+.help-block,
+.help-inline {
+ color: #595959;
+}
+
+.help-block {
+ display: block;
+ margin-bottom: 10px;
+}
+
+.help-inline {
+ display: inline-block;
+ *display: inline;
+ padding-left: 5px;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.input-append,
+.input-prepend {
+ margin-bottom: 5px;
+ font-size: 0;
+ white-space: nowrap;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+ position: relative;
+ margin-bottom: 0;
+ *margin-left: 0;
+ font-size: 14px;
+ vertical-align: top;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+ z-index: 2;
+}
+
+.input-append .add-on,
+.input-prepend .add-on {
+ display: inline-block;
+ width: auto;
+ height: 20px;
+ min-width: 16px;
+ padding: 4px 5px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+ text-align: center;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #eeeeee;
+ border: 1px solid #ccc;
+}
+
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn {
+ vertical-align: top;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-append .active,
+.input-prepend .active {
+ background-color: #a9dba9;
+ border-color: #46a546;
+}
+
+.input-prepend .add-on,
+.input-prepend .btn {
+ margin-right: -1px;
+}
+
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-append .add-on,
+.input-append .btn {
+ margin-left: -1px;
+}
+
+.input-append .add-on:last-child,
+.input-append .btn:last-child {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+ margin-right: -1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+ margin-left: -1px;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \9;
+ padding-left: 14px;
+ padding-left: 4px \9;
+ /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+ margin-bottom: 0;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+/* Allow for input prepend/append in search forms */
+
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.form-search .input-append .search-query {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search .input-append .btn {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .btn {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+ display: none;
+}
+
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+ display: inline-block;
+}
+
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+ float: left;
+ margin-right: 3px;
+ margin-left: 0;
+}
+
+.control-group {
+ margin-bottom: 10px;
+}
+
+legend + .control-group {
+ margin-top: 20px;
+ -webkit-margin-top-collapse: separate;
+}
+
+.form-horizontal .control-group {
+ margin-bottom: 20px;
+ *zoom: 1;
+}
+
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-horizontal .control-group:after {
+ clear: both;
+}
+
+.form-horizontal .control-label {
+ float: left;
+ width: 160px;
+ padding-top: 5px;
+ text-align: right;
+}
+
+.form-horizontal .controls {
+ *display: inline-block;
+ *padding-left: 20px;
+ margin-left: 180px;
+ *margin-left: 0;
+}
+
+.form-horizontal .controls:first-child {
+ *padding-left: 180px;
+}
+
+.form-horizontal .help-block {
+ margin-bottom: 0;
+}
+
+.form-horizontal input + .help-block,
+.form-horizontal select + .help-block,
+.form-horizontal textarea + .help-block {
+ margin-top: 10px;
+}
+
+.form-horizontal .form-actions {
+ padding-left: 180px;
+}
+
+table {
+ max-width: 100%;
+ background-color: transparent;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.table {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.table th,
+.table td {
+ padding: 8px;
+ line-height: 20px;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid #dddddd;
+}
+
+.table th {
+ font-weight: bold;
+}
+
+.table thead th {
+ vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table tbody + tbody {
+ border-top: 2px solid #dddddd;
+}
+
+.table-condensed th,
+.table-condensed td {
+ padding: 4px 5px;
+}
+
+.table-bordered {
+ border: 1px solid #dddddd;
+ border-collapse: separate;
+ *border-collapse: collapse;
+ border-left: 0;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.table-bordered th,
+.table-bordered td {
+ border-left: 1px solid #dddddd;
+}
+
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table-bordered thead:first-child tr:first-child th:first-child,
+.table-bordered tbody:first-child tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered thead:first-child tr:first-child th:last-child,
+.table-bordered tbody:first-child tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child th:first-child,
+.table-bordered tbody:last-child tr:last-child td:first-child,
+.table-bordered tfoot:last-child tr:last-child td:first-child {
+ -webkit-border-radius: 0 0 0 4px;
+ -moz-border-radius: 0 0 0 4px;
+ border-radius: 0 0 0 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child th:last-child,
+.table-bordered tbody:last-child tr:last-child td:last-child,
+.table-bordered tfoot:last-child tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-striped tbody tr:nth-child(odd) td,
+.table-striped tbody tr:nth-child(odd) th {
+ background-color: #f9f9f9;
+}
+
+.table-hover tbody tr:hover td,
+.table-hover tbody tr:hover th {
+ background-color: #f5f5f5;
+}
+
+table [class*=span],
+.row-fluid table [class*=span] {
+ display: table-cell;
+ float: none;
+ margin-left: 0;
+}
+
+.table .span1 {
+ float: none;
+ width: 44px;
+ margin-left: 0;
+}
+
+.table .span2 {
+ float: none;
+ width: 124px;
+ margin-left: 0;
+}
+
+.table .span3 {
+ float: none;
+ width: 204px;
+ margin-left: 0;
+}
+
+.table .span4 {
+ float: none;
+ width: 284px;
+ margin-left: 0;
+}
+
+.table .span5 {
+ float: none;
+ width: 364px;
+ margin-left: 0;
+}
+
+.table .span6 {
+ float: none;
+ width: 444px;
+ margin-left: 0;
+}
+
+.table .span7 {
+ float: none;
+ width: 524px;
+ margin-left: 0;
+}
+
+.table .span8 {
+ float: none;
+ width: 604px;
+ margin-left: 0;
+}
+
+.table .span9 {
+ float: none;
+ width: 684px;
+ margin-left: 0;
+}
+
+.table .span10 {
+ float: none;
+ width: 764px;
+ margin-left: 0;
+}
+
+.table .span11 {
+ float: none;
+ width: 844px;
+ margin-left: 0;
+}
+
+.table .span12 {
+ float: none;
+ width: 924px;
+ margin-left: 0;
+}
+
+.table .span13 {
+ float: none;
+ width: 1004px;
+ margin-left: 0;
+}
+
+.table .span14 {
+ float: none;
+ width: 1084px;
+ margin-left: 0;
+}
+
+.table .span15 {
+ float: none;
+ width: 1164px;
+ margin-left: 0;
+}
+
+.table .span16 {
+ float: none;
+ width: 1244px;
+ margin-left: 0;
+}
+
+.table .span17 {
+ float: none;
+ width: 1324px;
+ margin-left: 0;
+}
+
+.table .span18 {
+ float: none;
+ width: 1404px;
+ margin-left: 0;
+}
+
+.table .span19 {
+ float: none;
+ width: 1484px;
+ margin-left: 0;
+}
+
+.table .span20 {
+ float: none;
+ width: 1564px;
+ margin-left: 0;
+}
+
+.table .span21 {
+ float: none;
+ width: 1644px;
+ margin-left: 0;
+}
+
+.table .span22 {
+ float: none;
+ width: 1724px;
+ margin-left: 0;
+}
+
+.table .span23 {
+ float: none;
+ width: 1804px;
+ margin-left: 0;
+}
+
+.table .span24 {
+ float: none;
+ width: 1884px;
+ margin-left: 0;
+}
+
+.table tbody tr.success td {
+ background-color: #dff0d8;
+}
+
+.table tbody tr.error td {
+ background-color: #f2dede;
+}
+
+.table tbody tr.warning td {
+ background-color: #fcf8e3;
+}
+
+.table tbody tr.info td {
+ background-color: #d9edf7;
+}
+
+.table-hover tbody tr.success:hover td {
+ background-color: #d0e9c6;
+}
+
+.table-hover tbody tr.error:hover td {
+ background-color: #ebcccc;
+}
+
+.table-hover tbody tr.warning:hover td {
+ background-color: #faf2cc;
+}
+
+.table-hover tbody tr.info:hover td {
+ background-color: #c4e3f3;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ margin-top: 1px;
+ *margin-right: .3em;
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("../img/glyphicons-halflings.png");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+}
+
+/* White icons with optional class, or on hover/active states of certain elements */
+
+.icon-white,
+.nav-tabs > .active > a > [class^="icon-"],
+.nav-tabs > .active > a > [class*=" icon-"],
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"] {
+ background-image: url("../img/glyphicons-halflings-white.png");
+}
+
+.icon-glass {
+ background-position: 0 0;
+}
+
+.icon-music {
+ background-position: -24px 0;
+}
+
+.icon-search {
+ background-position: -48px 0;
+}
+
+.icon-envelope {
+ background-position: -72px 0;
+}
+
+.icon-heart {
+ background-position: -96px 0;
+}
+
+.icon-star {
+ background-position: -120px 0;
+}
+
+.icon-star-empty {
+ background-position: -144px 0;
+}
+
+.icon-user {
+ background-position: -168px 0;
+}
+
+.icon-film {
+ background-position: -192px 0;
+}
+
+.icon-th-large {
+ background-position: -216px 0;
+}
+
+.icon-th {
+ background-position: -240px 0;
+}
+
+.icon-th-list {
+ background-position: -264px 0;
+}
+
+.icon-ok {
+ background-position: -288px 0;
+}
+
+.icon-remove {
+ background-position: -312px 0;
+}
+
+.icon-zoom-in {
+ background-position: -336px 0;
+}
+
+.icon-zoom-out {
+ background-position: -360px 0;
+}
+
+.icon-off {
+ background-position: -384px 0;
+}
+
+.icon-signal {
+ background-position: -408px 0;
+}
+
+.icon-cog {
+ background-position: -432px 0;
+}
+
+.icon-trash {
+ background-position: -456px 0;
+}
+
+.icon-home {
+ background-position: 0 -24px;
+}
+
+.icon-file {
+ background-position: -24px -24px;
+}
+
+.icon-time {
+ background-position: -48px -24px;
+}
+
+.icon-road {
+ background-position: -72px -24px;
+}
+
+.icon-download-alt {
+ background-position: -96px -24px;
+}
+
+.icon-download {
+ background-position: -120px -24px;
+}
+
+.icon-upload {
+ background-position: -144px -24px;
+}
+
+.icon-inbox {
+ background-position: -168px -24px;
+}
+
+.icon-play-circle {
+ background-position: -192px -24px;
+}
+
+.icon-repeat {
+ background-position: -216px -24px;
+}
+
+.icon-refresh {
+ background-position: -240px -24px;
+}
+
+.icon-list-alt {
+ background-position: -264px -24px;
+}
+
+.icon-lock {
+ background-position: -287px -24px;
+}
+
+.icon-flag {
+ background-position: -312px -24px;
+}
+
+.icon-headphones {
+ background-position: -336px -24px;
+}
+
+.icon-volume-off {
+ background-position: -360px -24px;
+}
+
+.icon-volume-down {
+ background-position: -384px -24px;
+}
+
+.icon-volume-up {
+ background-position: -408px -24px;
+}
+
+.icon-qrcode {
+ background-position: -432px -24px;
+}
+
+.icon-barcode {
+ background-position: -456px -24px;
+}
+
+.icon-tag {
+ background-position: 0 -48px;
+}
+
+.icon-tags {
+ background-position: -25px -48px;
+}
+
+.icon-book {
+ background-position: -48px -48px;
+}
+
+.icon-bookmark {
+ background-position: -72px -48px;
+}
+
+.icon-print {
+ background-position: -96px -48px;
+}
+
+.icon-camera {
+ background-position: -120px -48px;
+}
+
+.icon-font {
+ background-position: -144px -48px;
+}
+
+.icon-bold {
+ background-position: -167px -48px;
+}
+
+.icon-italic {
+ background-position: -192px -48px;
+}
+
+.icon-text-height {
+ background-position: -216px -48px;
+}
+
+.icon-text-width {
+ background-position: -240px -48px;
+}
+
+.icon-align-left {
+ background-position: -264px -48px;
+}
+
+.icon-align-center {
+ background-position: -288px -48px;
+}
+
+.icon-align-right {
+ background-position: -312px -48px;
+}
+
+.icon-align-justify {
+ background-position: -336px -48px;
+}
+
+.icon-list {
+ background-position: -360px -48px;
+}
+
+.icon-indent-left {
+ background-position: -384px -48px;
+}
+
+.icon-indent-right {
+ background-position: -408px -48px;
+}
+
+.icon-facetime-video {
+ background-position: -432px -48px;
+}
+
+.icon-picture {
+ background-position: -456px -48px;
+}
+
+.icon-pencil {
+ background-position: 0 -72px;
+}
+
+.icon-map-marker {
+ background-position: -24px -72px;
+}
+
+.icon-adjust {
+ background-position: -48px -72px;
+}
+
+.icon-tint {
+ background-position: -72px -72px;
+}
+
+.icon-edit {
+ background-position: -96px -72px;
+}
+
+.icon-share {
+ background-position: -120px -72px;
+}
+
+.icon-check {
+ background-position: -144px -72px;
+}
+
+.icon-move {
+ background-position: -168px -72px;
+}
+
+.icon-step-backward {
+ background-position: -192px -72px;
+}
+
+.icon-fast-backward {
+ background-position: -216px -72px;
+}
+
+.icon-backward {
+ background-position: -240px -72px;
+}
+
+.icon-play {
+ background-position: -264px -72px;
+}
+
+.icon-pause {
+ background-position: -288px -72px;
+}
+
+.icon-stop {
+ background-position: -312px -72px;
+}
+
+.icon-forward {
+ background-position: -336px -72px;
+}
+
+.icon-fast-forward {
+ background-position: -360px -72px;
+}
+
+.icon-step-forward {
+ background-position: -384px -72px;
+}
+
+.icon-eject {
+ background-position: -408px -72px;
+}
+
+.icon-chevron-left {
+ background-position: -432px -72px;
+}
+
+.icon-chevron-right {
+ background-position: -456px -72px;
+}
+
+.icon-plus-sign {
+ background-position: 0 -96px;
+}
+
+.icon-minus-sign {
+ background-position: -24px -96px;
+}
+
+.icon-remove-sign {
+ background-position: -48px -96px;
+}
+
+.icon-ok-sign {
+ background-position: -72px -96px;
+}
+
+.icon-question-sign {
+ background-position: -96px -96px;
+}
+
+.icon-info-sign {
+ background-position: -120px -96px;
+}
+
+.icon-screenshot {
+ background-position: -144px -96px;
+}
+
+.icon-remove-circle {
+ background-position: -168px -96px;
+}
+
+.icon-ok-circle {
+ background-position: -192px -96px;
+}
+
+.icon-ban-circle {
+ background-position: -216px -96px;
+}
+
+.icon-arrow-left {
+ background-position: -240px -96px;
+}
+
+.icon-arrow-right {
+ background-position: -264px -96px;
+}
+
+.icon-arrow-up {
+ background-position: -289px -96px;
+}
+
+.icon-arrow-down {
+ background-position: -312px -96px;
+}
+
+.icon-share-alt {
+ background-position: -336px -96px;
+}
+
+.icon-resize-full {
+ background-position: -360px -96px;
+}
+
+.icon-resize-small {
+ background-position: -384px -96px;
+}
+
+.icon-plus {
+ background-position: -408px -96px;
+}
+
+.icon-minus {
+ background-position: -433px -96px;
+}
+
+.icon-asterisk {
+ background-position: -456px -96px;
+}
+
+.icon-exclamation-sign {
+ background-position: 0 -120px;
+}
+
+.icon-gift {
+ background-position: -24px -120px;
+}
+
+.icon-leaf {
+ background-position: -48px -120px;
+}
+
+.icon-fire {
+ background-position: -72px -120px;
+}
+
+.icon-eye-open {
+ background-position: -96px -120px;
+}
+
+.icon-eye-close {
+ background-position: -120px -120px;
+}
+
+.icon-warning-sign {
+ background-position: -144px -120px;
+}
+
+.icon-plane {
+ background-position: -168px -120px;
+}
+
+.icon-calendar {
+ background-position: -192px -120px;
+}
+
+.icon-random {
+ width: 16px;
+ background-position: -216px -120px;
+}
+
+.icon-comment {
+ background-position: -240px -120px;
+}
+
+.icon-magnet {
+ background-position: -264px -120px;
+}
+
+.icon-chevron-up {
+ background-position: -288px -120px;
+}
+
+.icon-chevron-down {
+ background-position: -313px -119px;
+}
+
+.icon-retweet {
+ background-position: -336px -120px;
+}
+
+.icon-shopping-cart {
+ background-position: -360px -120px;
+}
+
+.icon-folder-close {
+ background-position: -384px -120px;
+}
+
+.icon-folder-open {
+ width: 16px;
+ background-position: -408px -120px;
+}
+
+.icon-resize-vertical {
+ background-position: -432px -119px;
+}
+
+.icon-resize-horizontal {
+ background-position: -456px -118px;
+}
+
+.icon-hdd {
+ background-position: 0 -144px;
+}
+
+.icon-bullhorn {
+ background-position: -24px -144px;
+}
+
+.icon-bell {
+ background-position: -48px -144px;
+}
+
+.icon-certificate {
+ background-position: -72px -144px;
+}
+
+.icon-thumbs-up {
+ background-position: -96px -144px;
+}
+
+.icon-thumbs-down {
+ background-position: -120px -144px;
+}
+
+.icon-hand-right {
+ background-position: -144px -144px;
+}
+
+.icon-hand-left {
+ background-position: -168px -144px;
+}
+
+.icon-hand-up {
+ background-position: -192px -144px;
+}
+
+.icon-hand-down {
+ background-position: -216px -144px;
+}
+
+.icon-circle-arrow-right {
+ background-position: -240px -144px;
+}
+
+.icon-circle-arrow-left {
+ background-position: -264px -144px;
+}
+
+.icon-circle-arrow-up {
+ background-position: -288px -144px;
+}
+
+.icon-circle-arrow-down {
+ background-position: -312px -144px;
+}
+
+.icon-globe {
+ background-position: -336px -144px;
+}
+
+.icon-wrench {
+ background-position: -360px -144px;
+}
+
+.icon-tasks {
+ background-position: -384px -144px;
+}
+
+.icon-filter {
+ background-position: -408px -144px;
+}
+
+.icon-briefcase {
+ background-position: -432px -144px;
+}
+
+.icon-fullscreen {
+ background-position: -456px -144px;
+}
+
+.dropup,
+.dropdown {
+ position: relative;
+}
+
+.dropdown-toggle {
+ *margin-bottom: -3px;
+}
+
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-top: 4px solid #000000;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ list-style: none;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.dropdown-menu .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.dropdown-menu a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 20px;
+ color: #333333;
+ white-space: nowrap;
+}
+
+.dropdown-menu li > a:hover,
+.dropdown-menu li > a:focus,
+.dropdown-submenu:hover > a {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+ background-color: #0081c2;
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+ background-color: #0081c2;
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ outline: 0;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .disabled > a,
+.dropdown-menu .disabled > a:hover {
+ color: #999999;
+}
+
+.dropdown-menu .disabled > a:hover {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.open {
+ *z-index: 1000;
+}
+
+.open > .dropdown-menu {
+ display: block;
+}
+
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0;
+ border-bottom: 4px solid #000000;
+ content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+}
+
+.dropdown-submenu {
+ position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+ -webkit-border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px 6px;
+ border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover > .dropdown-menu {
+ display: block;
+}
+
+.dropdown-submenu > a:after {
+ display: block;
+ float: right;
+ width: 0;
+ height: 0;
+ margin-top: 5px;
+ margin-right: -10px;
+ border-color: transparent;
+ border-left-color: #cccccc;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ content: " ";
+}
+
+.dropdown-submenu:hover > a:after {
+ border-left-color: #ffffff;
+}
+
+.dropdown .dropdown-menu .nav-header {
+ padding-right: 20px;
+ padding-left: 20px;
+}
+
+.typeahead {
+ margin-top: 2px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-large {
+ padding: 24px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.well-small {
+ padding: 9px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity 0.15s linear;
+ -moz-transition: opacity 0.15s linear;
+ -o-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
+}
+
+.fade.in {
+ opacity: 1;
+}
+
+.collapse {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: height 0.35s ease;
+ -moz-transition: height 0.35s ease;
+ -o-transition: height 0.35s ease;
+ transition: height 0.35s ease;
+}
+
+.collapse.in {
+ height: auto;
+}
+
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+}
+
+.close:hover {
+ color: #000000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: 0.4;
+ filter: alpha(opacity=40);
+}
+
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+}
+
+.btn {
+ display: inline-block;
+ *display: inline;
+ padding: 4px 14px;
+ margin-bottom: 0;
+ *margin-left: .3em;
+ font-size: 14px;
+ line-height: 20px;
+ *line-height: 20px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ cursor: pointer;
+ background-color: #f5f5f5;
+ *background-color: #e6e6e6;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ border: 1px solid #bbbbbb;
+ *border: 0;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-bottom-color: #a2a2a2;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn:hover,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+ color: #333333;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+}
+
+.btn:active,
+.btn.active {
+ background-color: #cccccc \9;
+}
+
+.btn:first-child {
+ *margin-left: 0;
+}
+
+.btn:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+ /* Buttons in IE7 don't get borders, so darken on hover */
+
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+
+.btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.btn.active,
+.btn:active {
+ background-color: #e6e6e6;
+ background-color: #d9d9d9 \9;
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn.disabled,
+.btn[disabled] {
+ cursor: default;
+ background-color: #e6e6e6;
+ background-image: none;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-large {
+ padding: 9px 14px;
+ font-size: 16px;
+ line-height: normal;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.btn-large [class^="icon-"] {
+ margin-top: 2px;
+}
+
+.btn-small {
+ padding: 3px 9px;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.btn-small [class^="icon-"] {
+ margin-top: 0;
+}
+
+.btn-mini {
+ padding: 2px 6px;
+ font-size: 11px;
+ line-height: 17px;
+}
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+ width: 100%;
+}
+
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.btn {
+ border-color: #c5c5c5;
+ border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
+}
+
+.btn-primary {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #006dcc;
+ *background-color: #0044cc;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+ background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+ background-repeat: repeat-x;
+ border-color: #0044cc #0044cc #002a80;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+ color: #ffffff;
+ background-color: #0044cc;
+ *background-color: #003bb3;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #003399 \9;
+}
+
+.btn-warning {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #faa732;
+ *background-color: #f89406;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ border-color: #f89406 #f89406 #ad6704;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+ color: #ffffff;
+ background-color: #f89406;
+ *background-color: #df8505;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #c67605 \9;
+}
+
+.btn-danger {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #da4f49;
+ *background-color: #bd362f;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+ background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+ background-repeat: repeat-x;
+ border-color: #bd362f #bd362f #802420;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+ color: #ffffff;
+ background-color: #bd362f;
+ *background-color: #a9302a;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #942a25 \9;
+}
+
+.btn-success {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #5bb75b;
+ *background-color: #51a351;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+ background-image: -o-linear-gradient(top, #62c462, #51a351);
+ background-image: linear-gradient(to bottom, #62c462, #51a351);
+ background-image: -moz-linear-gradient(top, #62c462, #51a351);
+ background-repeat: repeat-x;
+ border-color: #51a351 #51a351 #387038;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+ color: #ffffff;
+ background-color: #51a351;
+ *background-color: #499249;
+}
+
+.btn-success:active,
+.btn-success.active {
+ background-color: #408140 \9;
+}
+
+.btn-info {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #49afcd;
+ *background-color: #2f96b4;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+ background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+ background-repeat: repeat-x;
+ border-color: #2f96b4 #2f96b4 #1f6377;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+ color: #ffffff;
+ background-color: #2f96b4;
+ *background-color: #2a85a0;
+}
+
+.btn-info:active,
+.btn-info.active {
+ background-color: #24748c \9;
+}
+
+.btn-inverse {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #363636;
+ *background-color: #222222;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+ background-image: -webkit-linear-gradient(top, #444444, #222222);
+ background-image: -o-linear-gradient(top, #444444, #222222);
+ background-image: linear-gradient(to bottom, #444444, #222222);
+ background-image: -moz-linear-gradient(top, #444444, #222222);
+ background-repeat: repeat-x;
+ border-color: #222222 #222222 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-inverse:hover,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+ color: #ffffff;
+ background-color: #222222;
+ *background-color: #151515;
+}
+
+.btn-inverse:active,
+.btn-inverse.active {
+ background-color: #080808 \9;
+}
+
+button.btn,
+input[type="submit"].btn {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+ background-color: transparent;
+ background-image: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-link {
+ color: #0088cc;
+ cursor: pointer;
+ border-color: transparent;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-link:hover {
+ color: #005580;
+ text-decoration: underline;
+ background-color: transparent;
+}
+
+.btn-link[disabled]:hover {
+ color: #333333;
+ text-decoration: none;
+}
+
+.btn-group {
+ position: relative;
+ *margin-left: .3em;
+ font-size: 0;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.btn-group:first-child {
+ *margin-left: 0;
+}
+
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-toolbar {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 0;
+}
+
+.btn-toolbar .btn-group {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-toolbar .btn + .btn,
+.btn-toolbar .btn-group + .btn,
+.btn-toolbar .btn + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-group > .btn {
+ position: relative;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group > .btn + .btn {
+ margin-left: -1px;
+}
+
+.btn-group > .btn,
+.btn-group > .dropdown-menu {
+ font-size: 14px;
+}
+
+.btn-group > .btn-mini {
+ font-size: 11px;
+}
+
+.btn-group > .btn-small {
+ font-size: 12px;
+}
+
+.btn-group > .btn-large {
+ font-size: 16px;
+}
+
+.btn-group > .btn:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.btn-group > .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ -webkit-border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ -moz-border-radius-topleft: 6px;
+}
+
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ -moz-border-radius-bottomright: 6px;
+}
+
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+ z-index: 2;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+ *padding-top: 5px;
+ padding-right: 8px;
+ *padding-bottom: 5px;
+ padding-left: 8px;
+ -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group > .btn-mini + .dropdown-toggle {
+ *padding-top: 2px;
+ padding-right: 5px;
+ *padding-bottom: 2px;
+ padding-left: 5px;
+}
+
+.btn-group > .btn-small + .dropdown-toggle {
+ *padding-top: 5px;
+ *padding-bottom: 4px;
+}
+
+.btn-group > .btn-large + .dropdown-toggle {
+ *padding-top: 7px;
+ padding-right: 12px;
+ *padding-bottom: 7px;
+ padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group.open .btn.dropdown-toggle {
+ background-color: #e6e6e6;
+}
+
+.btn-group.open .btn-primary.dropdown-toggle {
+ background-color: #0044cc;
+}
+
+.btn-group.open .btn-warning.dropdown-toggle {
+ background-color: #f89406;
+}
+
+.btn-group.open .btn-danger.dropdown-toggle {
+ background-color: #bd362f;
+}
+
+.btn-group.open .btn-success.dropdown-toggle {
+ background-color: #51a351;
+}
+
+.btn-group.open .btn-info.dropdown-toggle {
+ background-color: #2f96b4;
+}
+
+.btn-group.open .btn-inverse.dropdown-toggle {
+ background-color: #222222;
+}
+
+.btn .caret {
+ margin-top: 8px;
+ margin-left: 0;
+}
+
+.btn-mini .caret,
+.btn-small .caret,
+.btn-large .caret {
+ margin-top: 6px;
+}
+
+.btn-large .caret {
+ border-top-width: 5px;
+ border-right-width: 5px;
+ border-left-width: 5px;
+}
+
+.dropup .btn-large .caret {
+ border-top: 0;
+ border-bottom: 5px solid #000000;
+}
+
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.btn-group-vertical {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-group-vertical .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group-vertical .btn + .btn {
+ margin-top: -1px;
+ margin-left: 0;
+}
+
+.btn-group-vertical .btn:first-child {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.btn-group-vertical .btn:last-child {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.btn-group-vertical .btn-large:first-child {
+ -webkit-border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
+}
+
+.btn-group-vertical .btn-large:last-child {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 20px;
+ color: #c09853;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ background-color: #fcf8e3;
+ border: 1px solid #fbeed5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.alert h4 {
+ margin: 0;
+}
+
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: 20px;
+}
+
+.alert-success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.alert-danger,
+.alert-error {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.alert-info {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+
+.alert-block p + p {
+ margin-top: 5px;
+}
+
+.nav {
+ margin-bottom: 20px;
+ margin-left: 0;
+ list-style: none;
+}
+
+.nav > li > a {
+ display: block;
+}
+
+.nav > li > a:hover {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+
+.nav > .pull-right {
+ float: right;
+}
+
+.nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #999999;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-transform: uppercase;
+}
+
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+
+.nav-list {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-bottom: 0;
+}
+
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-right: -15px;
+ margin-left: -15px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ background-color: #0088cc;
+}
+
+.nav-list [class^="icon-"] {
+ margin-right: 2px;
+}
+
+.nav-list .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.nav-tabs,
+.nav-pills {
+ *zoom: 1;
+}
+
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.nav-tabs:after,
+.nav-pills:after {
+ clear: both;
+}
+
+.nav-tabs > li,
+.nav-pills > li {
+ float: left;
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: 20px;
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+ color: #555555;
+ cursor: default;
+ background-color: #ffffff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+ color: #ffffff;
+ background-color: #0088cc;
+}
+
+.nav-stacked > li {
+ float: none;
+}
+
+.nav-stacked > li > a {
+ margin-right: 0;
+}
+
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.nav-tabs.nav-stacked > li:first-child > a {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li:last-child > a {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li > a:hover {
+ z-index: 2;
+ border-color: #ddd;
+}
+
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px;
+}
+
+.nav-tabs .dropdown-menu {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.nav-pills .dropdown-menu {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.nav .dropdown-toggle .caret {
+ margin-top: 6px;
+ border-top-color: #0088cc;
+ border-bottom-color: #0088cc;
+}
+
+.nav .dropdown-toggle:hover .caret {
+ border-top-color: #005580;
+ border-bottom-color: #005580;
+}
+
+/* move down carets for tabs */
+
+.nav-tabs .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.nav .active .dropdown-toggle .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+
+.nav-tabs .active .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.nav > .dropdown.active > a:hover {
+ cursor: pointer;
+}
+
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover {
+ color: #ffffff;
+ background-color: #999999;
+ border-color: #999999;
+}
+
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.tabs-stacked .open > a:hover {
+ border-color: #999999;
+}
+
+.tabbable {
+ *zoom: 1;
+}
+
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.tabbable:after {
+ clear: both;
+}
+
+.tab-content {
+ overflow: auto;
+}
+
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+ border-bottom: 0;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+.tabs-below > .nav-tabs {
+ border-top: 1px solid #ddd;
+}
+
+.tabs-below > .nav-tabs > li {
+ margin-top: -1px;
+ margin-bottom: 0;
+}
+
+.tabs-below > .nav-tabs > li > a {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.tabs-below > .nav-tabs > li > a:hover {
+ border-top-color: #ddd;
+ border-bottom-color: transparent;
+}
+
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover {
+ border-color: transparent #ddd #ddd #ddd;
+}
+
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+ float: none;
+}
+
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+ min-width: 74px;
+ margin-right: 0;
+ margin-bottom: 3px;
+}
+
+.tabs-left > .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+
+.tabs-left > .nav-tabs > li > a {
+ margin-right: -1px;
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.tabs-left > .nav-tabs > li > a:hover {
+ border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: #ffffff;
+}
+
+.tabs-right > .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+
+.tabs-right > .nav-tabs > li > a {
+ margin-left: -1px;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.tabs-right > .nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: #ffffff;
+}
+
+.nav > .disabled > a {
+ color: #999999;
+}
+
+.nav > .disabled > a:hover {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.navbar {
+ *position: relative;
+ *z-index: 2;
+ margin-bottom: 20px;
+ overflow: visible;
+ color: #777777;
+}
+
+.navbar-inner {
+ min-height: 40px;
+ padding-right: 20px;
+ padding-left: 20px;
+ background-color: #fafafa;
+ background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+ background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+ background-repeat: repeat-x;
+ border: 1px solid #d4d4d4;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+}
+
+.navbar-inner:before,
+.navbar-inner:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-inner:after {
+ clear: both;
+}
+
+.navbar .container {
+ width: auto;
+}
+
+.nav-collapse.collapse {
+ height: auto;
+}
+
+.navbar .brand {
+ display: block;
+ float: left;
+ padding: 10px 20px 10px;
+ margin-left: -20px;
+ font-size: 20px;
+ font-weight: 200;
+ color: #777777;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .brand:hover {
+ text-decoration: none;
+}
+
+.navbar-text {
+ margin-bottom: 0;
+ line-height: 40px;
+}
+
+.navbar-link {
+ color: #777777;
+}
+
+.navbar-link:hover {
+ color: #333333;
+}
+
+.navbar .divider-vertical {
+ height: 40px;
+ margin: 0 9px;
+ border-right: 1px solid #ffffff;
+ border-left: 1px solid #f2f2f2;
+}
+
+.navbar .btn,
+.navbar .btn-group {
+ margin-top: 5px;
+}
+
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn {
+ margin-top: 0;
+}
+
+.navbar-form {
+ margin-bottom: 0;
+ *zoom: 1;
+}
+
+.navbar-form:before,
+.navbar-form:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-form:after {
+ clear: both;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+ margin-top: 5px;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+ display: inline-block;
+ margin-bottom: 0;
+}
+
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+ margin-top: 3px;
+}
+
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+ margin-top: 6px;
+ white-space: nowrap;
+}
+
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+ margin-top: 0;
+}
+
+.navbar-search {
+ position: relative;
+ float: left;
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+.navbar-search .search-query {
+ padding: 4px 14px;
+ margin-bottom: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 1;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.navbar-static-top {
+ position: static;
+ width: 100%;
+ margin-bottom: 0;
+}
+
+.navbar-static-top .navbar-inner {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+ margin-bottom: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ border-width: 1px 0 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.navbar-fixed-top {
+ top: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-fixed-bottom {
+ bottom: 0;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0 0;
+}
+
+.navbar .nav.pull-right {
+ float: right;
+ margin-right: 0;
+}
+
+.navbar .nav > li {
+ float: left;
+}
+
+.navbar .nav > li > a {
+ float: none;
+ padding: 10px 15px 10px;
+ color: #777777;
+ text-decoration: none;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .nav .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: transparent;
+}
+
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+ color: #555555;
+ text-decoration: none;
+ background-color: #e5e5e5;
+ -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+
+.navbar .btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-right: 5px;
+ margin-left: 5px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #ededed;
+ *background-color: #e5e5e5;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+ background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+ background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-repeat: repeat-x;
+ border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #e5e5e5;
+ *background-color: #d9d9d9;
+}
+
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+ background-color: #cccccc \9;
+}
+
+.navbar .btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+ -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+
+.navbar .nav > li > .dropdown-menu:before {
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ display: inline-block;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-left: 7px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ content: '';
+}
+
+.navbar .nav > li > .dropdown-menu:after {
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ display: inline-block;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ border-left: 6px solid transparent;
+ content: '';
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+ top: auto;
+ bottom: -7px;
+ border-top: 7px solid #ccc;
+ border-bottom: 0;
+ border-top-color: rgba(0, 0, 0, 0.2);
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+ top: auto;
+ bottom: -6px;
+ border-top: 6px solid #ffffff;
+ border-bottom: 0;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #555555;
+ background-color: #e5e5e5;
+}
+
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #777777;
+ border-bottom-color: #777777;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+ right: 12px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+ right: 13px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+ right: 100%;
+ left: auto;
+ margin-right: -1px;
+ margin-left: 0;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
+.navbar-inverse {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-inner {
+ background-color: #1b1b1b;
+ background-image: -moz-linear-gradient(top, #222222, #111111);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+ background-image: -webkit-linear-gradient(top, #222222, #111111);
+ background-image: -o-linear-gradient(top, #222222, #111111);
+ background-image: linear-gradient(to bottom, #222222, #111111);
+ background-repeat: repeat-x;
+ border-color: #252525;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+}
+
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+ color: #999999;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+}
+
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+ background-color: transparent;
+}
+
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .navbar-link {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+ color: #ffffff;
+}
+
+.navbar-inverse .divider-vertical {
+ border-right-color: #222222;
+ border-left-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #999999;
+ border-bottom-color: #999999;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-search .search-query {
+ color: #ffffff;
+ background-color: #515151;
+ border-color: #111111;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+ padding: 5px 15px;
+ color: #333333;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #ffffff;
+ border: 0;
+ outline: 0;
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-inverse .btn-navbar {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e0e0e;
+ *background-color: #040404;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+ background-image: -webkit-linear-gradient(top, #151515, #040404);
+ background-image: -o-linear-gradient(top, #151515, #040404);
+ background-image: linear-gradient(to bottom, #151515, #040404);
+ background-image: -moz-linear-gradient(top, #151515, #040404);
+ background-repeat: repeat-x;
+ border-color: #040404 #040404 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #040404;
+ *background-color: #000000;
+}
+
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+ background-color: #000000 \9;
+}
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin: 0 0 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.breadcrumb li {
+ display: inline-block;
+ *display: inline;
+ text-shadow: 0 1px 0 #ffffff;
+ *zoom: 1;
+}
+
+.breadcrumb .divider {
+ padding: 0 5px;
+ color: #ccc;
+}
+
+.breadcrumb .active {
+ color: #999999;
+}
+
+.pagination {
+ height: 40px;
+ margin: 20px 0;
+}
+
+.pagination ul {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ margin-left: 0;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.pagination ul > li {
+ display: inline;
+}
+
+.pagination ul > li > a,
+.pagination ul > li > span {
+ float: left;
+ padding: 0 14px;
+ line-height: 38px;
+ text-decoration: none;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-left-width: 0;
+}
+
+.pagination ul > li > a:hover,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ background-color: #f5f5f5;
+}
+
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ color: #999999;
+ cursor: default;
+}
+
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover {
+ color: #999999;
+ cursor: default;
+ background-color: transparent;
+}
+
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+ border-left-width: 1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.pagination-centered {
+ text-align: center;
+}
+
+.pagination-right {
+ text-align: right;
+}
+
+.pager {
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+ *zoom: 1;
+}
+
+.pager:before,
+.pager:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.pager:after {
+ clear: both;
+}
+
+.pager li {
+ display: inline;
+}
+
+.pager a,
+.pager span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.pager a:hover {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+
+.pager .next a,
+.pager .next span {
+ float: right;
+}
+
+.pager .previous a {
+ float: left;
+}
+
+.pager .disabled a,
+.pager .disabled a:hover,
+.pager .disabled span {
+ color: #999999;
+ cursor: default;
+ background-color: #fff;
+}
+
+.modal-open .modal .dropdown-menu {
+ z-index: 2050;
+}
+
+.modal-open .modal .dropdown.open {
+ *z-index: 2050;
+}
+
+.modal-open .modal .popover {
+ z-index: 2060;
+}
+
+.modal-open .modal .tooltip {
+ z-index: 2080;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000000;
+}
+
+.modal-backdrop.fade {
+ opacity: 0;
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: 1050;
+ width: 560px;
+ margin: -250px 0 0 -280px;
+ overflow: auto;
+ background-color: #ffffff;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ *border: 1px solid #999;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
+}
+
+.modal.fade {
+ top: -25%;
+ -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+ transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.modal.fade.in {
+ top: 50%;
+}
+
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+}
+
+.modal-header .close {
+ margin-top: 2px;
+}
+
+.modal-header h3 {
+ margin: 0;
+ line-height: 30px;
+}
+
+.modal-body {
+ max-height: 400px;
+ padding: 15px;
+ overflow-y: auto;
+}
+
+.modal-form {
+ margin-bottom: 0;
+}
+
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.modal-footer:after {
+ clear: both;
+}
+
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1030;
+ display: block;
+ padding: 5px;
+ font-size: 11px;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ visibility: visible;
+}
+
+.tooltip.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.tooltip.top {
+ margin-top: -3px;
+}
+
+.tooltip.right {
+ margin-left: 3px;
+}
+
+.tooltip.bottom {
+ margin-top: 3px;
+}
+
+.tooltip.left {
+ margin-left: -3px;
+}
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #ffffff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000000;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-right-color: #000000;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-left-color: #000000;
+ border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ width: 236px;
+ padding: 1px;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.popover.top {
+ margin-bottom: 10px;
+}
+
+.popover.right {
+ margin-left: 10px;
+}
+
+.popover.bottom {
+ margin-top: 10px;
+}
+
+.popover.left {
+ margin-right: 10px;
+}
+
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+ padding: 9px 14px;
+}
+
+.popover-content p,
+.popover-content ul,
+.popover-content ol {
+ margin-bottom: 0;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.popover .arrow:after {
+ z-index: -1;
+ content: "";
+}
+
+.popover.top .arrow {
+ bottom: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-top-color: #ffffff;
+ border-width: 10px 10px 0;
+}
+
+.popover.top .arrow:after {
+ bottom: -1px;
+ left: -11px;
+ border-top-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 11px 0;
+}
+
+.popover.right .arrow {
+ top: 50%;
+ left: -10px;
+ margin-top: -10px;
+ border-right-color: #ffffff;
+ border-width: 10px 10px 10px 0;
+}
+
+.popover.right .arrow:after {
+ bottom: -11px;
+ left: -1px;
+ border-right-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 11px 11px 0;
+}
+
+.popover.bottom .arrow {
+ top: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-bottom-color: #ffffff;
+ border-width: 0 10px 10px;
+}
+
+.popover.bottom .arrow:after {
+ top: -1px;
+ left: -11px;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+ border-width: 0 11px 11px;
+}
+
+.popover.left .arrow {
+ top: 50%;
+ right: -10px;
+ margin-top: -10px;
+ border-left-color: #ffffff;
+ border-width: 10px 0 10px 10px;
+}
+
+.popover.left .arrow:after {
+ right: -1px;
+ bottom: -11px;
+ border-left-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 0 11px 11px;
+}
+
+.thumbnails {
+ margin-left: -20px;
+ list-style: none;
+ *zoom: 1;
+}
+
+.thumbnails:before,
+.thumbnails:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.thumbnails:after {
+ clear: both;
+}
+
+.row-fluid .thumbnails {
+ margin-left: 0;
+}
+
+.thumbnails > li {
+ float: left;
+ margin-bottom: 20px;
+ margin-left: 20px;
+}
+
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: 20px;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -webkit-transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+}
+
+a.thumbnail:hover {
+ border-color: #0088cc;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.thumbnail .caption {
+ padding: 9px;
+ color: #555555;
+}
+
+.label,
+.badge {
+ font-size: 11.844px;
+ font-weight: bold;
+ line-height: 14px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999999;
+}
+
+.label {
+ padding: 1px 4px 2px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.badge {
+ padding: 1px 9px 2px;
+ -webkit-border-radius: 9px;
+ -moz-border-radius: 9px;
+ border-radius: 9px;
+}
+
+a.label:hover,
+a.badge:hover {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.label-important,
+.badge-important {
+ background-color: #b94a48;
+}
+
+.label-important[href],
+.badge-important[href] {
+ background-color: #953b39;
+}
+
+.label-warning,
+.badge-warning {
+ background-color: #f89406;
+}
+
+.label-warning[href],
+.badge-warning[href] {
+ background-color: #c67605;
+}
+
+.label-success,
+.badge-success {
+ background-color: #468847;
+}
+
+.label-success[href],
+.badge-success[href] {
+ background-color: #356635;
+}
+
+.label-info,
+.badge-info {
+ background-color: #3a87ad;
+}
+
+.label-info[href],
+.badge-info[href] {
+ background-color: #2d6987;
+}
+
+.label-inverse,
+.badge-inverse {
+ background-color: #333333;
+}
+
+.label-inverse[href],
+.badge-inverse[href] {
+ background-color: #1a1a1a;
+}
+
+.btn .label,
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+.btn-mini .label,
+.btn-mini .badge {
+ top: 0;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+ background-repeat: repeat-x;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress .bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ color: #ffffff;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e90d2;
+ background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+ background-image: -o-linear-gradient(top, #149bdf, #0480be);
+ background-image: linear-gradient(to bottom, #149bdf, #0480be);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-transition: width 0.6s ease;
+ -moz-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
+}
+
+.progress .bar + .bar {
+ -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+}
+
+.progress-striped .bar {
+ background-color: #149bdf;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ -webkit-background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+
+.progress.active .bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-danger .bar,
+.progress .bar-danger {
+ background-color: #dd514c;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+ background-color: #ee5f5b;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-success .bar,
+.progress .bar-success {
+ background-color: #5eb95e;
+ background-image: -moz-linear-gradient(top, #62c462, #57a957);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+ background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+ background-image: -o-linear-gradient(top, #62c462, #57a957);
+ background-image: linear-gradient(to bottom, #62c462, #57a957);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+ background-color: #62c462;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-info .bar,
+.progress .bar-info {
+ background-color: #4bb1cf;
+ background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+ background-color: #5bc0de;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-warning .bar,
+.progress .bar-warning {
+ background-color: #faa732;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+ background-color: #fbb450;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.accordion {
+ margin-bottom: 20px;
+}
+
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.accordion-heading {
+ border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+
+.accordion-toggle {
+ cursor: pointer;
+}
+
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+ position: relative;
+ margin-bottom: 20px;
+ line-height: 1;
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+
+.carousel .item {
+ position: relative;
+ display: none;
+ -webkit-transition: 0.6s ease-in-out left;
+ -moz-transition: 0.6s ease-in-out left;
+ -o-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
+}
+
+.carousel .item > img {
+ display: block;
+ line-height: 1;
+}
+
+.carousel .active,
+.carousel .next,
+.carousel .prev {
+ display: block;
+}
+
+.carousel .active {
+ left: 0;
+}
+
+.carousel .next,
+.carousel .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.carousel .next {
+ left: 100%;
+}
+
+.carousel .prev {
+ left: -100%;
+}
+
+.carousel .next.left,
+.carousel .prev.right {
+ left: 0;
+}
+
+.carousel .active.left {
+ left: -100%;
+}
+
+.carousel .active.right {
+ left: 100%;
+}
+
+.carousel-control {
+ position: absolute;
+ top: 40%;
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ margin-top: -20px;
+ font-size: 60px;
+ font-weight: 100;
+ line-height: 30px;
+ color: #ffffff;
+ text-align: center;
+ background: #222222;
+ border: 3px solid #ffffff;
+ -webkit-border-radius: 23px;
+ -moz-border-radius: 23px;
+ border-radius: 23px;
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.carousel-control.right {
+ right: 15px;
+ left: auto;
+}
+
+.carousel-control:hover {
+ color: #ffffff;
+ text-decoration: none;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.carousel-caption {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 15px;
+ background: #333333;
+ background: rgba(0, 0, 0, 0.75);
+}
+
+.carousel-caption h4,
+.carousel-caption p {
+ line-height: 20px;
+ color: #ffffff;
+}
+
+.carousel-caption h4 {
+ margin: 0 0 5px;
+}
+
+.carousel-caption p {
+ margin-bottom: 0;
+}
+
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ background-color: #eeeeee;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.hero-unit h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+ color: inherit;
+}
+
+.hero-unit p {
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 30px;
+ color: inherit;
+}
+
+.pull-right {
+ float: right;
+}
+
+.pull-left {
+ float: left;
+}
+
+.hide {
+ display: none;
+}
+
+.show {
+ display: block;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+.affix {
+ position: fixed;
+} \ No newline at end of file
diff --git a/docs/css/default.css b/docs/css/default.css
new file mode 100644
index 00000000..57446ff9
--- /dev/null
+++ b/docs/css/default.css
@@ -0,0 +1,268 @@
+/* Set the body padding-top when above 980px to push the content down from
+ below the navbar, which is fixed at >980px screen widths. */
+pre {
+ font-size: 12px;
+}
+
+.dropdown .dropdown-menu {
+ display: none;
+}
+
+.dropdown.open .dropdown-menu {
+ display: block;
+}
+
+@media (max-width: 480px) {
+ .repo-link {
+ display: none;
+ }
+}
+
+/* Header link to GitHub */
+.repo-link {
+ float: right;
+ margin-right: 10px;
+ margin-top: 9px;
+}
+
+/* GitHub 'Star' badge */
+body.index-page #main-content iframe {
+ float: right;
+ margin-top: -12px;
+ margin-right: -15px;
+}
+
+/* Travis CI badge */
+body.index-page #main-content p:first-of-type {
+ float: right;
+ margin-right: 8px;
+ margin-top: -14px;
+ margin-bottom: 0px;
+}
+
+/* Github source file badges */
+a.github {
+ float: right;
+ margin-top: -12px;
+ margin-right: 12px;
+}
+
+a.github:hover {
+ text-decoration: none;
+}
+
+/* */
+body hr {
+ border-top: 1px dotted #A30000;
+}
+
+/* Force TOC text to not overrun */
+#table-of-contents {
+ overflow: hidden;
+ margin: 0 0 20px 0;
+}
+
+/* Code blocks should scroll horizontally */
+pre {
+ overflow: auto;
+ word-wrap: normal;
+ white-space: pre;
+}
+
+/* Preserve the spacing of the navbar across different screen sizes. */
+.navbar-inner {
+ /*padding: 5px 0;*/
+}
+
+@media (max-width: 979px) {
+ .navbar .brand {
+ margin-left: 0;
+ padding-left: 0;
+ }
+ .navbar-inner .container-fluid {
+ padding-left: 15px;
+ }
+}
+
+.nav-list li.main {
+ font-weight: bold;
+}
+
+.nav-list a {
+ overflow: hidden;
+}
+
+/* Set the table of contents to static so it flows back into the content when
+ viewed on tablets and smaller. */
+@media (max-width: 767px) {
+ #table-of-contents {
+ position: static;
+ }
+}
+
+/* When the page is in two-column layout, give the main content some room
+ to breath on the left. */
+@media (min-width: 768px) {
+ #main-content {
+ padding-left: 1em;
+ }
+}
+
+/* Cutesy quote styling */
+blockquote {
+ font-family: Georgia, serif;
+ font-size: 18px;
+ font-style: italic;
+ margin: 0.25em 0;
+ padding: 0.25em 40px;
+ line-height: 1.45;
+ position: relative;
+ color: #383838;
+ border-left: none;
+}
+
+blockquote:before {
+ display: block;
+ content: "\201C";
+ font-size: 80px;
+ position: absolute;
+ left: -10px;
+ top: -20px;
+ color: #7a7a7a;
+}
+
+blockquote p:last-child {
+ color: #999999;
+ font-size: 14px;
+ display: block;
+ margin-top: 5px;
+}
+
+
+/*=== dabapps bootstrap styles ====*/
+
+html{
+ width:100%;
+ background: none;
+}
+
+body, .navbar .navbar-inner .container-fluid{
+ max-width: 1150px;
+ margin: 0 auto;
+}
+
+body{
+ background: url("../img/grid.png") repeat-x;
+ background-attachment: fixed;
+}
+
+/* custom navigation styles */
+
+.navbar .navbar-inner{
+ background: #2C2C2C;
+ color: white;
+ border: none;
+ border-top: 5px solid #A30000;
+}
+
+.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand{
+ color: white;
+}
+
+.nav-list > .active > a, .navbar .navbar-inner .nav li a:hover {
+ background:#212121;
+ color:white;
+}
+
+.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li{
+ color: #A30000;
+}
+.navbar .navbar-inner .dropdown-menu li a:hover{
+ background: #eeeeee;
+ color: #c20000;
+}
+
+/* custom general page styles */
+.hero-unit h2, .hero-unit h1{
+ color: #A30000;
+}
+
+body a{
+ color: #A30000;
+}
+
+body a:hover{
+ color: #c20000;
+}
+
+/* subnavigation styles */
+
+@media (min-width: 767px) {
+ .sidebar-nav-fixed {
+ position:fixed;
+ width:19%;
+ max-width: 240px;
+ }
+
+ .navbar {
+ position: fixed;
+ }
+ .navbar .navbar-inner .container-fluid{
+ max-width: 1110px;
+ }
+}
+
+/* sticky footer and footer */
+html, body {
+ height: 100%;
+}
+.wrapper {
+ min-height: 100%;
+ height: auto !important;
+ height: 100%;
+ margin: 0 auto -60px;
+}
+
+.body-content{
+ padding-top: 70px;
+ padding-bottom: 70px;
+}
+
+@media (max-width: 979px) {
+ .navbar-fixed-top .navbar-inner {
+ padding: 0px;
+ }
+}
+
+@media (max-width: 767px) {
+ .body-content{
+ padding-top: 0px;
+ }
+}
+
+@media (min-width: 768px) {
+ footer.span12 {
+ width: 95%;
+ }
+}
+
+footer, .push {
+ height: 60px; /* .push must be the same height as .footer */
+}
+
+
+footer p {
+ text-align: center;
+ color: gray;
+ border-top: 1px solid #DDD;
+ padding-top: 10px;
+}
+
+footer a {
+ color: gray;
+ font-weight: bold;
+}
+
+footer a:hover {
+ color: gray;
+}
diff --git a/docs/css/prettify.css b/docs/css/prettify.css
new file mode 100644
index 00000000..d437aff6
--- /dev/null
+++ b/docs/css/prettify.css
@@ -0,0 +1,30 @@
+.com { color: #93a1a1; }
+.lit { color: #195f91; }
+.pun, .opn, .clo { color: #93a1a1; }
+.fun { color: #dc322f; }
+.str, .atv { color: #D14; }
+.kwd, .prettyprint .tag { color: #1e347b; }
+.typ, .atn, .dec, .var { color: teal; }
+.pln { color: #48484c; }
+
+.prettyprint {
+ padding: 8px;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+.prettyprint.linenums {
+ -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
+ -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
+ box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
+}
+
+/* Specify class=linenums on a pre to get line numbering */
+ol.linenums {
+ margin: 0 0 0 33px; /* IE indents via margin-left */
+}
+ol.linenums li {
+ padding-left: 12px;
+ color: #bebec5;
+ line-height: 20px;
+ text-shadow: 0 1px 0 #fff;
+} \ No newline at end of file
diff --git a/docs/examples.rst b/docs/examples.rst
deleted file mode 100644
index 29e6a24c..00000000
--- 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://shielded-mountain-6732.herokuapp.com
-
-(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 1d191af3..00000000
--- a/docs/examples/blogpost.rst
+++ /dev/null
@@ -1,37 +0,0 @@
-Blog Posts API
-==============
-
-* http://shielded-mountain-6732.herokuapp.com/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 9bd27045..00000000
--- 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://shielded-mountain-6732.herokuapp.com/model-resource-example/
-
- You can browse the API using a web browser, or from the command line::
-
- curl -X GET http://shielded-mountain-6732.herokuapp.com/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 <http://docs.djangoproject.com/en/dev/topics/forms/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://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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 81c1fcb6..00000000
--- a/docs/examples/objectstore.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-Object Store API
-================
-
-* http://shielded-mountain-6732.herokuapp.com/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 a806d751..00000000
--- 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://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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 4d1bb6ca..00000000
--- 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 <http://pygments.org/>`_ library, to create the Web API for a simple pastebin.
-
-.. note::
-
- A live sandbox instance of this API is available at http://shielded-mountain-6732.herokuapp.com/pygments/
-
- You can browse the API using a web browser, or from the command line::
-
- curl -X GET http://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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 ec465aaf..00000000
--- 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 829b10d5..00000000
--- 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://shielded-mountain-6732.herokuapp.com/resource-example/
-
- You can browse the API using a web browser, or from the command line::
-
- curl -X GET http://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/resource-example/1/
- {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}}
diff --git a/docs/howto.rst b/docs/howto.rst
deleted file mode 100644
index 8fdc0926..00000000
--- 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 0f668335..00000000
--- 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 <https://bitbucket.org/jespern/django-piston/wiki/Home>`_ is very mature, and has a large community behind it. This project was originally based on piston code in parts.
-* `django-tastypie <https://github.com/toastdriven/django-tastypie>`_ is also very good, and has a very active and helpful developer community and maintainers.
-* Other interesting projects include `dagny <https://github.com/zacharyvoase/dagny>`_ and `dj-webmachine <http://benoitc.github.com/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 98a31123..00000000
--- 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://shielded-mountain-6732.herokuapp.com/mixin/
-
- You can browse the API using a web browser, or from the command line::
-
- curl -X GET http://shielded-mountain-6732.herokuapp.com/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/reverse.rst b/docs/howto/reverse.rst
deleted file mode 100644
index 0a7af0e9..00000000
--- a/docs/howto/reverse.rst
+++ /dev/null
@@ -1,50 +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=None)
--------------------------------
-
-The `reverse` function has the same behavior as
-`django.core.urlresolvers.reverse`_, except that it optionally takes a request
-keyword argument, which it uses to return a fully qualified URL.
-
- from djangorestframework.reverse import reverse
- from djangorestframework.views import View
-
- class MyView(View):
- def get(self, request):
- context = {
- 'url': reverse('year-summary', args=[1945], request=request)
- }
-
- return Response(context)
-
-reverse_lazy(viewname, ..., request=None)
-------------------------------------
-
-The `reverse_lazy` function has the same behavior as
-`django.core.urlresolvers.reverse_lazy`_, except that it optionally takes a
-request keyword argument, which it uses to return a fully qualified URL.
-
-.. _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 f0127060..00000000
--- 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 %}
- <h1 id="site-name">My API</h1>
- {% 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 eeb8da06..00000000
--- a/docs/howto/usingcurl.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-Using CURL with django-rest-framework
-=====================================
-
-`curl <http://curl.haxx.se/>`_ 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 <user>:<password> http://example.com/my-api/
diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst
deleted file mode 100644
index a14f8ddc..00000000
--- 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://shielded-mountain-6732.herokuapp.com/model-resource-example')
- >>> r.getcode() # Check if the response was ok
- 200
- >>> print r.read() # Examin the response itself
- [{"url": "http://shielded-mountain-6732.herokuapp.com/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://shielded-mountain-6732.herokuapp.com/model-resource-example/', data=d, headers={'Content-Type':'application/x-www-form-urlencoded'})
- >>> resp = urllib2.urlopen(req)
- >>> resp.getcode()
- 201
- >>> resp.read()
- '{"url": "http://shielded-mountain-6732.herokuapp.com/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/img/favicon.ico b/docs/img/favicon.ico
new file mode 100644
index 00000000..917892ad
--- /dev/null
+++ b/docs/img/favicon.ico
Binary files differ
diff --git a/docs/img/glyphicons-halflings-white.png b/docs/img/glyphicons-halflings-white.png
new file mode 100644
index 00000000..3bf6484a
--- /dev/null
+++ b/docs/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/docs/img/glyphicons-halflings.png b/docs/img/glyphicons-halflings.png
new file mode 100644
index 00000000..a9969993
--- /dev/null
+++ b/docs/img/glyphicons-halflings.png
Binary files differ
diff --git a/docs/img/grid.png b/docs/img/grid.png
new file mode 100644
index 00000000..878c3ed5
--- /dev/null
+++ b/docs/img/grid.png
Binary files differ
diff --git a/docs/img/quickstart.png b/docs/img/quickstart.png
new file mode 100644
index 00000000..5006d60f
--- /dev/null
+++ b/docs/img/quickstart.png
Binary files differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..a96d0925
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,208 @@
+<iframe src="http://ghbtns.com/github-btn.html?user=tomchristie&amp;repo=django-rest-framework&amp;type=watch&amp;count=true" allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
+[![Travis build image][travis-build-image]][travis]
+
+# Django REST framework
+
+**A toolkit for building well-connected, self-describing Web APIs.**
+
+---
+
+**Note**: This documentation is for the 2.0 version of REST framework. If you are looking for earlier versions please see the [0.4.x branch][0.4] on GitHub.
+
+---
+
+Django REST framework is a lightweight library that makes it easy to build Web APIs. It is designed as a modular and easy to customize architecture, based on Django's class based views.
+
+Web APIs built using REST framework are fully self-describing and web browseable - a huge useability win for your developers. It also supports a wide range of media types, authentication and permission policies out of the box.
+
+If you are considering using REST framework for your API, we recommend reading the [REST framework 2 announcment][rest-framework-2-announcement] which gives a good overview of the framework and it's capabilities.
+
+There is also a sandbox API you can use for testing purposes, [available here][sandbox].
+
+**Below**: *Screenshot from the browseable API*
+
+![Screenshot][image]
+
+## Requirements
+
+REST framework requires the following:
+
+* Python (2.6, 2.7)
+* Django (1.3, 1.4, 1.5)
+
+The following packages are optional:
+
+* [Markdown][markdown] (2.1.0+) - Markdown support for the self describing API.
+* [PyYAML][yaml] (3.10+) - YAML content-type support.
+
+## Installation
+
+Install using `pip`, including any optional packages you want...
+
+ pip install djangorestframework
+ pip install markdown # Recommended if using the browseable API.
+ pip install pyyaml # Required for yaml content-type support.
+
+...or clone the project from github.
+
+ git clone git@github.com:tomchristie/django-rest-framework.git
+ cd django-rest-framework
+ pip install -r requirements.txt
+ pip install -r optionals.txt
+
+Add `rest_framework` to your `INSTALLED_APPS`.
+
+ INSTALLED_APPS = (
+ ...
+ 'rest_framework',
+ )
+
+If you're intending to use the browseable API you'll want to add REST framework's login and logout views. Add the following to your root `urls.py` file.
+
+ urlpatterns = patterns('',
+ ...
+ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
+ )
+
+Note that the URL path can be whatever you want, but you must include `rest_framework.urls` with the `rest_framework` namespace.
+
+<!--
+## Quickstart
+
+Can't wait to get started? The [quickstart guide][quickstart] is the fastest way to get up and running with REST framework.
+-->
+
+## Tutorial
+
+The 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, and is highly recommended reading.
+
+* [1 - Serialization][tut-1]
+* [2 - Requests & Responses][tut-2]
+* [3 - Class based views][tut-3]
+* [4 - Authentication & permissions][tut-4]
+* [5 - Relationships & hyperlinked APIs][tut-5]
+
+## API Guide
+
+The API guide is your complete reference manual to all the functionality provided by REST framework.
+
+* [Requests][request]
+* [Responses][response]
+* [Views][views]
+* [Generic views][generic-views]
+* [Parsers][parsers]
+* [Renderers][renderers]
+* [Serializers][serializers]
+* [Serializer fields][fields]
+* [Authentication][authentication]
+* [Permissions][permissions]
+* [Throttling][throttling]
+* [Pagination][pagination]
+* [Content negotiation][contentnegotiation]
+* [Format suffixes][formatsuffixes]
+* [Returning URLs][reverse]
+* [Exceptions][exceptions]
+* [Status codes][status]
+* [Settings][settings]
+
+## Topics
+
+General guides to using REST framework.
+
+* [Browser enhancements][browser-enhancements]
+* [The Browsable API][browsableapi]
+* [REST, Hypermedia & HATEOAS][rest-hypermedia-hateoas]
+* [2.0 Announcement][rest-framework-2-announcement]
+* [Release Notes][release-notes]
+* [Credits][credits]
+
+## Development
+
+If you want to work on REST framework itself, clone the repository, then...
+
+Build the docs:
+
+ ./mkdocs.py
+
+Run the tests:
+
+ ./rest_framework/runtests/runtests.py
+
+## Support
+
+For support please see the [REST framework discussion group][group], or try the `#restframework` channel on `irc.freenode.net`.
+
+Paid support is also available from [DabApps], and can include work on REST framework core, or support with building your REST framework API. Please contact [Tom Christie][email] if you'd like to discuss commercial support options.
+
+## License
+
+Copyright (c) 2011-2012, Tom Christie
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+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.
+
+[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=restframework2
+[travis-build-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=restframework2
+[urlobject]: https://github.com/zacharyvoase/urlobject
+[markdown]: http://pypi.python.org/pypi/Markdown/
+[yaml]: http://pypi.python.org/pypi/PyYAML
+[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
+[image]: img/quickstart.png
+[sandbox]: http://restframework.herokuapp.com/
+
+[quickstart]: tutorial/quickstart.md
+[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-and-permissions.md
+[tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md
+
+[request]: api-guide/requests.md
+[response]: api-guide/responses.md
+[views]: api-guide/views.md
+[generic-views]: api-guide/generic-views.md
+[parsers]: api-guide/parsers.md
+[renderers]: api-guide/renderers.md
+[serializers]: api-guide/serializers.md
+[fields]: api-guide/fields.md
+[authentication]: api-guide/authentication.md
+[permissions]: api-guide/permissions.md
+[throttling]: api-guide/throttling.md
+[pagination]: api-guide/pagination.md
+[contentnegotiation]: api-guide/content-negotiation.md
+[formatsuffixes]: api-guide/format-suffixes.md
+[reverse]: api-guide/reverse.md
+[exceptions]: api-guide/exceptions.md
+[status]: api-guide/status-codes.md
+[settings]: api-guide/settings.md
+
+[csrf]: topics/csrf.md
+[browser-enhancements]: topics/browser-enhancements.md
+[browsableapi]: topics/browsable-api.md
+[rest-hypermedia-hateoas]: topics/rest-hypermedia-hateoas.md
+[contributing]: topics/contributing.md
+[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md
+[release-notes]: topics/release-notes.md
+[credits]: topics/credits.md
+
+[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
+[DabApps]: http://dabapps.com
+[email]: mailto:tom@tomchristie.com
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index ac681a00..00000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. meta::
- :description: A lightweight REST framework for Django.
- :keywords: django, python, REST, RESTful, API, interface, framework
-
-
-Django REST framework
-=====================
-
-**The documentation has moved.**
-
-**Please see:** `http://django-rest-framework.org <http://django-rest-framework.org>`_
diff --git a/docs/js/bootstrap-2.1.1-min.js b/docs/js/bootstrap-2.1.1-min.js
new file mode 100755
index 00000000..e0b220f4
--- /dev/null
+++ b/docs/js/bootstrap-2.1.1-min.js
@@ -0,0 +1,7 @@
+/**
+* Bootstrap.js by @fat & @mdo
+* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-affix.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;a("body").addClass("modal-open"),this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1).focus(),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,a("body").removeClass("modal-open"),this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(a){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(this.removeBackdrop,this)):this.removeBackdrop()):b&&b()}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f).one("hide",function(){c.focus()})})})}(window.jQuery),!function(a){function d(){e(a(b)).removeClass("open")}function e(b){var c=b.attr("data-target"),d;return c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,"")),d=a(c),d.length||(d=b.parent()),d}var b="[data-toggle=dropdown]",c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),f,g;if(c.is(".disabled, :disabled"))return;return f=e(c),g=f.hasClass("open"),d(),g||(f.toggleClass("open"),c.focus()),!1},keydown:function(b){var c,d,f,g,h,i;if(!/(38|40|27)/.test(b.keyCode))return;c=a(this),b.preventDefault(),b.stopPropagation();if(c.is(".disabled, :disabled"))return;g=e(c),h=g.hasClass("open");if(!h||h&&b.keyCode==27)return c.click();d=a("[role=menu] li:not(.divider) a",g);if(!d.length)return;i=d.index(d.filter(":focus")),b.keyCode==38&&i>0&&i--,b.keyCode==40&&i<d.length-1&&i++,~i||(i=0),d.eq(i).focus()}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api touchstart.dropdown.data-api",d),a("body").on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",b,c.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",b+", [role=menu]",c.prototype.keydown)})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll-spy.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var b=a(this),c=b.data("target")||b.attr("href"),d=/^#\w/.test(c)&&a(c);return d&&d.length&&[[d.position().top,c]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu").length&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this)):this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f+"."+this.type,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();return c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove(),this},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(a){var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content > *")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(a){var b=function(b,c){this.options=a.extend({},a.fn.affix.defaults,c),this.$window=a(window).on("scroll.affix.data-api",a.proxy(this.checkPosition,this)),this.$element=a(b),this.checkPosition()};b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var b=a(document).height(),c=this.$window.scrollTop(),d=this.$element.offset(),e=this.options.offset,f=e.bottom,g=e.top,h="affix affix-top affix-bottom",i;typeof e!="object"&&(f=g=e),typeof g=="function"&&(g=e.top()),typeof f=="function"&&(f=e.bottom()),i=this.unpin!=null&&c+this.unpin<=d.top?!1:f!=null&&d.top+this.$element.height()>=b-f?"bottom":g!=null&&c<=g?"top":!1;if(this.affixed===i)return;this.affixed=i,this.unpin=i=="bottom"?d.top-c:null,this.$element.removeClass(h).addClass("affix"+(i?"-"+i:""))},a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("affix"),f=typeof c=="object"&&c;e||d.data("affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.defaults={offset:0},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery),!function(a){var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),a.support.transition&&this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();c[a(e).hasClass("in")?"addClass":"removeClass"]("collapsed"),a(e).collapse(f)})})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".item.active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide",{relatedTarget:e[0]});this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(c=a.isFunction(this.source)?this.source(this.query,a.proxy(this.process,this)):this.source,c?this.process(c):this)},process:function(b){var c=this;return b=a.grep(b,function(a){return c.matcher(a)}),b=this.sorter(b),b.length?this.render(b.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.chrome||a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keydown,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},move:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},keydown:function(b){this.suppressKeyPressRepeat=!~a.inArray(b.keyCode,[40,38,9,13,27]),this.move(b)},keypress:function(a){if(this.suppressKeyPressRepeat)return;this.move(a)},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery) \ No newline at end of file
diff --git a/docs/js/jquery-1.8.1-min.js b/docs/js/jquery-1.8.1-min.js
new file mode 100644
index 00000000..3b8d15d0
--- /dev/null
+++ b/docs/js/jquery-1.8.1-min.js
@@ -0,0 +1,2 @@
+/*! jQuery v@1.8.1 jquery.com | jquery.org/license */
+(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.1",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":a.toString().replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||f.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return typeof a=="object"?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length||!d)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||++p.uuid:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")===0&&(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)~f.indexOf(" "+b[g]+" ")||(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>-1)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,""+d),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,k,l,m,n=(p._data(this,"events")||{})[c.type]||[],o=n.delegateCount,q=[].slice.call(arguments),r=!c.exclusive&&!c.namespace,s=p.event.special[c.type]||{},t=[];q[0]=c,c.delegateTarget=this;if(s.preDispatch&&s.preDispatch.call(this,c)===!1)return;if(o&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<o;d++)k=n[d],l=k.selector,h[l]===b&&(h[l]=p(l,this).index(f)>=0),h[l]&&j.push(k);j.length&&t.push({elem:f,matches:j})}n.length>o&&t.push({elem:this,matches:n.slice(o)});for(d=0;d<t.length&&!c.isPropagationStopped();d++){i=t[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){k=i.matches[e];if(r||!c.namespace&&!k.namespace||c.namespace_re&&c.namespace_re.test(k.namespace))c.data=k.data,c.handleObj=k,g=((p.event.special[k.origType]||{}).handle||k.handler).apply(i.elem,q),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return s.postDispatch&&s.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function $(a,b,c,d){c=c||[],b=b||q;var e,f,g,j,k=b.nodeType;if(k!==1&&k!==9)return[];if(!a||typeof a!="string")return c;g=h(b);if(!g&&!d)if(e=L.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&i(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return u.apply(c,t.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&X&&b.getElementsByClassName)return u.apply(c,t.call(b.getElementsByClassName(j),0)),c}return bk(a,b,c,d,g)}function _(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function ba(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bb(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bc(a,b,c,d){var e,g,h,i,j,k,l,m,n,p,r=!c&&b!==q,s=(r?"<s>":"")+a.replace(H,"$1<s>"),u=y[o][s];if(u)return d?0:t.call(u,0);j=a,k=[],m=0,n=f.preFilter,p=f.filter;while(j){if(!e||(g=I.exec(j)))g&&(j=j.slice(g[0].length),h.selector=l),k.push(h=[]),l="",r&&(j=" "+j);e=!1;if(g=J.exec(j))l+=g[0],j=j.slice(g[0].length),e=h.push({part:g.pop().replace(H," "),string:g[0],captures:g});for(i in p)(g=S[i].exec(j))&&(!n[i]||(g=n[i](g,b,c)))&&(l+=g[0],j=j.slice(g[0].length),e=h.push({part:i,string:g.shift(),captures:g}));if(!e)break}return l&&(h.selector=l),d?j.length:j?$.error(a):t.call(y(s,k),0)}function bd(a,b,e,f){var g=b.dir,h=s++;return a||(a=function(a){return a===e}),b.first?function(b){while(b=b[g])if(b.nodeType===1)return a(b)&&b}:f?function(b){while(b=b[g])if(b.nodeType===1&&a(b))return b}:function(b){var e,f=h+"."+c,i=f+"."+d;while(b=b[g])if(b.nodeType===1){if((e=b[o])===i)return b.sizset;if(typeof e=="string"&&e.indexOf(f)===0){if(b.sizset)return b}else{b[o]=i;if(a(b))return b.sizset=!0,b;b.sizset=!1}}}}function be(a,b){return a?function(c){var d=b(c);return d&&a(d===!0?c:d)}:b}function bf(a,b,c){var d,e,g=0;for(;d=a[g];g++)f.relative[d.part]?e=bd(e,f.relative[d.part],b,c):e=be(e,f.filter[d.part].apply(null,d.captures.concat(b,c)));return e}function bg(a){return function(b){var c,d=0;for(;c=a[d];d++)if(c(b))return!0;return!1}}function bh(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)$(a,b[e],c,d)}function bi(a,b,c,d,e,g){var h,i=f.setFilters[b.toLowerCase()];return i||$.error(b),(a||!(h=e))&&bh(a||"*",d,h=[],e),h.length>0?i(h,c,g):[]}function bj(a,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s=0,t=a.length,v=S.POS,w=new RegExp("^"+v.source+"(?!"+A+")","i"),x=function(){var a=1,c=arguments.length-2;for(;a<c;a++)arguments[a]===b&&(n[a]=b)};for(;s<t;s++){f=a[s],g="",m=e;for(h=0,i=f.length;h<i;h++){j=f[h],k=j.string;if(j.part==="PSEUDO"){v.exec(""),l=0;while(n=v.exec(k)){o=!0,p=v.lastIndex=n.index+n[0].length;if(p>l){g+=k.slice(l,n.index),l=p,q=[c],J.test(g)&&(m&&(q=m),m=e);if(r=O.test(g))g=g.slice(0,-5).replace(J,"$&*"),l++;n.length>1&&n[0].replace(w,x),m=bi(g,n[1],n[2],q,m,r)}g=""}}o||(g+=k),o=!1}g?J.test(g)?bh(g,m||[c],d,e):$(g,c,d,e?e.concat(m):m):u.apply(d,m)}return t===1?d:$.uniqueSort(d)}function bk(a,b,e,g,h){a=a.replace(H,"$1");var i,k,l,m,n,o,p,q,r,s,v=bc(a,b,h),w=b.nodeType;if(S.POS.test(a))return bj(v,b,e,g);if(g)i=t.call(g,0);else if(v.length===1){if((o=t.call(v[0],0)).length>2&&(p=o[0]).part==="ID"&&w===9&&!h&&f.relative[o[1].part]){b=f.find.ID(p.captures[0].replace(R,""),b,h)[0];if(!b)return e;a=a.slice(o.shift().string.length)}r=(v=N.exec(o[0].string))&&!v.index&&b.parentNode||b,q="";for(n=o.length-1;n>=0;n--){p=o[n],s=p.part,q=p.string+q;if(f.relative[s])break;if(f.order.test(s)){i=f.find[s](p.captures[0].replace(R,""),r,h);if(i==null)continue;a=a.slice(0,a.length-q.length)+q.replace(S[s],""),a||u.apply(e,t.call(i,0));break}}}if(a){k=j(a,b,h),c=k.dirruns++,i==null&&(i=f.find.TAG("*",N.test(a)&&b.parentNode||b));for(n=0;m=i[n];n++)d=k.runs++,k(m)&&e.push(m)}return e}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=a.document,r=q.documentElement,s=0,t=[].slice,u=[].push,v=function(a,b){return a[o]=b||!0,a},w=function(){var a={},b=[];return v(function(c,d){return b.push(c)>f.cacheLength&&delete a[b.shift()],a[c]=d},a)},x=w(),y=w(),z=w(),A="[\\x20\\t\\r\\n\\f]",B="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",C=B.replace("w","w#"),D="([*^$|!~]?=)",E="\\["+A+"*("+B+")"+A+"*(?:"+D+A+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+C+")|)|)"+A+"*\\]",F=":("+B+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+E+")|[^:]|\\\\.)*|.*))\\)|)",G=":(nth|eq|gt|lt|first|last|even|odd)(?:\\(((?:-\\d)?\\d*)\\)|)(?=[^-]|$)",H=new RegExp("^"+A+"+|((?:^|[^\\\\])(?:\\\\.)*)"+A+"+$","g"),I=new RegExp("^"+A+"*,"+A+"*"),J=new RegExp("^"+A+"*([\\x20\\t\\r\\n\\f>+~])"+A+"*"),K=new RegExp(F),L=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,M=/^:not/,N=/[\x20\t\r\n\f]*[+~]/,O=/:not\($/,P=/h\d/i,Q=/input|select|textarea|button/i,R=/\\(?!\\)/g,S={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),NAME:new RegExp("^\\[name=['\"]?("+B+")['\"]?\\]"),TAG:new RegExp("^("+B.replace("w","w*")+")"),ATTR:new RegExp("^"+E),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|nth|last|first)-child(?:\\("+A+"*(even|odd|(([+-]|)(\\d*)n|)"+A+"*(?:([+-]|)"+A+"*(\\d+)|))"+A+"*\\)|)","i"),POS:new RegExp(G,"ig"),needsContext:new RegExp("^"+A+"*[>+~]|"+G,"i")},T=function(a){var b=q.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},U=T(function(a){return a.appendChild(q.createComment("")),!a.getElementsByTagName("*").length}),V=T(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),W=T(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),X=T(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),Y=T(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",r.insertBefore(a,r.firstChild);var b=q.getElementsByName&&q.getElementsByName(o).length===2+q.getElementsByName(o+0).length;return e=!q.getElementById(o),r.removeChild(a),b});try{t.call(r.childNodes,0)[0].nodeType}catch(Z){t=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}$.matches=function(a,b){return $(a,null,null,b)},$.matchesSelector=function(a,b){return $(b,null,null,[a]).length>0},g=$.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=g(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=g(b);return c},h=$.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},i=$.contains=r.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:r.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},$.attr=function(a,b){var c,d=h(a);return d||(b=b.toLowerCase()),f.attrHandle[b]?f.attrHandle[b](a):W||d?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},f=$.selectors={cacheLength:50,createPseudo:v,match:S,order:new RegExp("ID|TAG"+(Y?"|NAME":"")+(X?"|CLASS":"")),attrHandle:V?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:e?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:U?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(R,""),a[3]=(a[4]||a[5]||"").replace(R,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||$.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&$.error(a[0]),a},PSEUDO:function(a,b,c){var d,e;if(S.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(d=a[4])K.test(d)&&(e=bc(d,b,c,!0))&&(e=d.indexOf(")",d.length-e)-d.length)&&(d=d.slice(0,e),a[0]=a[0].slice(0,e)),a[2]=d;return a.slice(0,3)}},filter:{ID:e?function(a){return a=a.replace(R,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(R,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(R,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=x[o][a];return b||(b=x(a,new RegExp("(^|"+A+")"+a+"("+A+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return b?function(d){var e=$.attr(d,a),f=e+"";if(e==null)return b==="!=";switch(b){case"=":return f===c;case"!=":return f!==c;case"^=":return c&&f.indexOf(c)===0;case"*=":return c&&f.indexOf(c)>-1;case"$=":return c&&f.substr(f.length-c.length)===c;case"~=":return(" "+f+" ").indexOf(c)>-1;case"|=":return f===c||f.substr(0,c.length+1)===c+"-"}}:function(b){return $.attr(b,a)!=null}},CHILD:function(a,b,c,d){if(a==="nth"){var e=s++;return function(a){var b,f,g=0,h=a;if(c===1&&d===0)return!0;b=a.parentNode;if(b&&(b[o]!==e||!a.sizset)){for(h=b.firstChild;h;h=h.nextSibling)if(h.nodeType===1){h.sizset=++g;if(h===a)break}b[o]=e}return f=a.sizset-d,c===0?f===0:f%c===0&&f/c>=0}}return function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b,c,d){var e,g=f.pseudos[a]||f.pseudos[a.toLowerCase()];return g||$.error("unsupported pseudo: "+a),g[o]?g(b,c,d):g.length>1?(e=[a,a,"",b],function(a){return g(a,0,e)}):g}},pseudos:{not:v(function(a,b,c){var d=j(a.replace(H,"$1"),b,c);return function(a){return!d(a)}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!f.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},contains:v(function(a){return function(b){return(b.textContent||b.innerText||g(b)).indexOf(a)>-1}}),has:v(function(a){return function(b){return $(a,b).length>0}}),header:function(a){return P.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:_("radio"),checkbox:_("checkbox"),file:_("file"),password:_("password"),image:_("image"),submit:ba("submit"),reset:ba("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return Q.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b,c){return c?a.slice(1):[a[0]]},last:function(a,b,c){var d=a.pop();return c?a:[d]},even:function(a,b,c){var d=[],e=c?1:0,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},odd:function(a,b,c){var d=[],e=c?0:1,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},lt:function(a,b,c){return c?a.slice(+b):a.slice(0,+b)},gt:function(a,b,c){return c?a.slice(0,+b+1):a.slice(+b+1)},eq:function(a,b,c){var d=a.splice(+b,1);return c?a:d}}},k=r.compareDocumentPosition?function(a,b){return a===b?(l=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return l=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bb(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bb(e[j],f[j]);return j===c?bb(a,f[j],-1):bb(e[j],b,1)},[0,0].sort(k),m=!l,$.uniqueSort=function(a){var b,c=1;l=m,a.sort(k);if(l)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},$.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},j=$.compile=function(a,b,c){var d,e,f,g=z[o][a];if(g&&g.context===b)return g;d=bc(a,b,c);for(e=0,f=d.length;e<f;e++)d[e]=bf(d[e],b,c);return g=z(a,bg(d)),g.context=b,g.runs=g.dirruns=0,g},q.querySelectorAll&&function(){var a,b=bk,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[],f=[":active"],g=r.matchesSelector||r.mozMatchesSelector||r.webkitMatchesSelector||r.oMatchesSelector||r.msMatchesSelector;T(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+A+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),T(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+A+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=e.length&&new RegExp(e.join("|")),bk=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a)))if(d.nodeType===9)try{return u.apply(f,t.call(d.querySelectorAll(a),0)),f}catch(i){}else if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){var j,k,l,m=d.getAttribute("id"),n=m||o,p=N.test(a)&&d.parentNode||d;m?n=n.replace(c,"\\$&"):d.setAttribute("id",n),j=bc(a,d,h),n="[id='"+n+"']";for(k=0,l=j.length;k<l;k++)j[k]=n+j[k].selector;try{return u.apply(f,t.call(p.querySelectorAll(j.join(",")),0)),f}catch(i){}finally{m||d.removeAttribute("id")}}return b(a,d,f,g,h)},g&&(T(function(b){a=g.call(b,"div");try{g.call(b,"[test!='']:sizzle"),f.push(S.PSEUDO.source,S.POS.source,"!=")}catch(c){}}),f=new RegExp(f.join("|")),$.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!h(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=g.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return $(c,null,null,[b]).length>0})}(),f.setFilters.nth=f.setFilters.eq,f.filters=f.pseudos,$.attr=p.attr,p.find=$,p.expr=$.selectors,p.expr[":"]=p.expr.pseudos,p.unique=$.uniqueSort,p.text=$.getText,p.isXMLDoc=$.isXML,p.contains=$.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{cj=f.href}catch(cy){cj=e.createElement("a"),cj.href="",cj=cj.href}ck=ct.exec(cj.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:cj,isLocal:cn.test(ck[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=""+(c||y),k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,ck[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase()),l.crossDomain=!(!i||i[1]==ck[1]&&i[2]==ck[2]&&(i[3]||(i[1]==="http:"?80:443))==(ck[3]||(ck[1]==="http:"?80:443)))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e,f=this.createTween(a,b),g=cQ.exec(b),h=f.cur(),i=+h||0,j=1;if(g){c=+g[2],d=g[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&i){i=p.css(f.elem,a,!0)||c||1;do e=j=j||".5",i=i/j,p.style(f.elem,a,i+d),j=f.cur()/h;while(j!==1&&j!==e)}f.unit=d,f.start=i,f.end=g[1]?i+(g[1]+1)*c:c}return f}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j,k,l,m=this[0],n=m&&m.ownerDocument;if(!n)return;return(e=n.body)===m?p.offset.bodyOffset(m):(d=n.documentElement,p.contains(d,m)?(c=m.getBoundingClientRect(),f=da(n),g=d.clientTop||e.clientTop||0,h=d.clientLeft||e.clientLeft||0,i=f.pageYOffset||d.scrollTop,j=f.pageXOffset||d.scrollLeft,k=c.top+i-g,l=c.left+j-h,{top:k,left:l}):{top:0,left:0})},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file
diff --git a/docs/js/prettify-1.0.js b/docs/js/prettify-1.0.js
new file mode 100644
index 00000000..eef5ad7e
--- /dev/null
+++ b/docs/js/prettify-1.0.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
diff --git a/docs/library.rst b/docs/library.rst
deleted file mode 100644
index b0309da0..00000000
--- 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 d159f605..00000000
--- 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 93fb081a..00000000
--- 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 04bf66b0..00000000
--- 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 48d762a5..00000000
--- 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 c694d639..00000000
--- 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 a9e72931..00000000
--- 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/resource.rst b/docs/library/resource.rst
deleted file mode 100644
index 2a95051b..00000000
--- 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 c2fff5a7..00000000
--- 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 a2c29c48..00000000
--- 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 63dd3308..00000000
--- 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 0c7596bc..00000000
--- a/docs/library/status.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:mod:`status`
-===============
-
-.. automodule:: status
- :members:
diff --git a/docs/library/views.rst b/docs/library/views.rst
deleted file mode 100644
index 329b487b..00000000
--- a/docs/library/views.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:mod:`views`
-=====================
-
-.. automodule:: views
- :members:
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 46a67149..a91fb978 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,8 +1 @@
-# 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
+markdown>=2.1.0
diff --git a/docs/template.html b/docs/template.html
new file mode 100644
index 00000000..94fc269f
--- /dev/null
+++ b/docs/template.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html lang="en">
+<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <title>Django REST framework</title>
+ <link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="{{ base_url }}/css/prettify.css" rel="stylesheet">
+ <link href="{{ base_url }}/css/bootstrap.css" rel="stylesheet">
+ <link href="{{ base_url }}/css/bootstrap-responsive.css" rel="stylesheet">
+ <link href="{{ base_url }}/css/default.css" rel="stylesheet">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <script type="text/javascript">
+
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-18852272-2']);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+
+ </script>
+ </head>
+ <body onload="prettyPrint()" class="{{ page_id }}-page">
+
+ <div class="wrapper">
+
+ <div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container-fluid">
+ <a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/master">GitHub</a>
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="{{ base_url }}{{ index }}">Django REST framework</a>
+ <div class="nav-collapse collapse">
+ <ul class="nav">
+ <li><a href="{{ base_url }}{{ index }}">Home</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Tutorial <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <!--<li><a href="{{ base_url }}/tutorial/quickstart{{ suffix }}">Quickstart</a></li>-->
+ <li><a href="{{ base_url }}/tutorial/1-serialization{{ suffix }}">1 - Serialization</a></li>
+ <li><a href="{{ base_url }}/tutorial/2-requests-and-responses{{ suffix }}">2 - Requests and responses</a></li>
+ <li><a href="{{ base_url }}/tutorial/3-class-based-views{{ suffix }}">3 - Class based views</a></li>
+ <li><a href="{{ base_url }}/tutorial/4-authentication-and-permissions{{ suffix }}">4 - Authentication and permissions</a></li>
+ <li><a href="{{ base_url }}/tutorial/5-relationships-and-hyperlinked-apis{{ suffix }}">5 - Relationships and hyperlinked APIs</a></li>
+ </ul>
+ </li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="{{ base_url }}/api-guide/requests{{ suffix }}">Requests</a></li>
+ <li><a href="{{ base_url }}/api-guide/responses{{ suffix }}">Responses</a></li>
+ <li><a href="{{ base_url }}/api-guide/views{{ suffix }}">Views</a></li>
+ <li><a href="{{ base_url }}/api-guide/generic-views{{ suffix }}">Generic views</a></li>
+ <li><a href="{{ base_url }}/api-guide/parsers{{ suffix }}">Parsers</a></li>
+ <li><a href="{{ base_url }}/api-guide/renderers{{ suffix }}">Renderers</a></li>
+ <li><a href="{{ base_url }}/api-guide/serializers{{ suffix }}">Serializers</a></li>
+ <li><a href="{{ base_url }}/api-guide/fields{{ suffix }}">Serializer fields</a></li>
+ <li><a href="{{ base_url }}/api-guide/authentication{{ suffix }}">Authentication</a></li>
+ <li><a href="{{ base_url }}/api-guide/permissions{{ suffix }}">Permissions</a></li>
+ <li><a href="{{ base_url }}/api-guide/throttling{{ suffix }}">Throttling</a></li>
+ <li><a href="{{ base_url }}/api-guide/pagination{{ suffix }}">Pagination</a></li>
+ <li><a href="{{ base_url }}/api-guide/content-negotiation{{ suffix }}">Content negotiation</a></li>
+ <li><a href="{{ base_url }}/api-guide/format-suffixes{{ suffix }}">Format suffixes</a></li>
+ <li><a href="{{ base_url }}/api-guide/reverse{{ suffix }}">Returning URLs</a></li>
+ <li><a href="{{ base_url }}/api-guide/exceptions{{ suffix }}">Exceptions</a></li>
+ <li><a href="{{ base_url }}/api-guide/status-codes{{ suffix }}">Status codes</a></li>
+ <li><a href="{{ base_url }}/api-guide/settings{{ suffix }}">Settings</a></li>
+ </ul>
+ </li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="{{ base_url }}/topics/browser-enhancements{{ suffix }}">Browser enhancements</a></li>
+ <li><a href="{{ base_url }}/topics/browsable-api{{ suffix }}">The Browsable API</a></li>
+ <li><a href="{{ base_url }}/topics/rest-hypermedia-hateoas{{ suffix }}">REST, Hypermedia & HATEOAS</a></li>
+ <li><a href="{{ base_url }}/topics/rest-framework-2-announcement{{ suffix }}">2.0 Announcement</a></li>
+ <li><a href="{{ base_url }}/topics/release-notes{{ suffix }}">Release Notes</a></li>
+ <li><a href="{{ base_url }}/topics/credits{{ suffix }}">Credits</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav pull-right">
+ <!-- TODO
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Version: 2.0.0 <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Trunk</a></li>
+ <li><a href="#">2.0.0</a></li>
+ </ul>
+ </li>
+ -->
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+ </div>
+
+ <div class="body-content">
+ <div class="container-fluid">
+ <div class="row-fluid">
+
+ <div class="span3">
+ <!-- TODO
+ <p style="margin-top: -12px">
+ <a class="btn btn-mini btn-primary" style="width: 60px">&laquo; previous</a>
+ <a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next &raquo;</a>
+ </p>
+ -->
+ <div id="table-of-contents">
+ <ul class="nav nav-list side-nav well sidebar-nav-fixed">
+ {{ toc }}
+ </ul>
+ </div>
+ </div>
+
+ <div id="main-content" class="span9">
+ {{ content }}
+ </div><!--/span-->
+ </div><!--/row-->
+ </div><!--/.fluid-container-->
+ </div><!--/.body content-->
+
+ <div id="push"></div>
+ </div><!--/.wrapper -->
+
+ <footer class="span12">
+ <p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</a></p>
+ </footer>
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="{{ base_url }}/js/jquery-1.8.1-min.js"></script>
+ <script src="{{ base_url }}/js/prettify-1.0.js"></script>
+ <script src="{{ base_url }}/js/bootstrap-2.1.1-min.js"></script>
+ <script>
+ //$('.side-nav').scrollspy()
+ var shiftWindow = function() { scrollBy(0, -50) };
+ if (location.hash) shiftWindow();
+ window.addEventListener("hashchange", shiftWindow);
+
+ $('.dropdown-menu').on('click touchstart', function(event) {
+ event.stopPropagation();
+ });
+ </script>
+</body></html>
diff --git a/docs/templates/layout.html b/docs/templates/layout.html
deleted file mode 100644
index a59645f2..00000000
--- 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 %}<title>{% if pagename == 'index' %}Django REST framework{% else %}{{ titleprefix }}{{ title|striptags|e }}{% endif %}</title>{% endblock %}
-
-{% block extrahead %}
-{{ super() }}
-<script type="text/javascript">
-
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-18852272-2']);
- _gaq.push(['_trackPageview']);
-
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
-
-</script>
-{% endblock %}
-{% block footer %}
-<div class="footer"> <p> Documentation version {{ version }} {% endblock %}</p></div>
diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md
new file mode 100644
index 00000000..9fe82e69
--- /dev/null
+++ b/docs/topics/browsable-api.md
@@ -0,0 +1,102 @@
+# The Browsable API
+
+> It is a profoundly erroneous truism... that we should cultivate the habit of thinking of what we are doing. The precise opposite is the case. Civilization advances by extending the number of important operations which we can perform without thinking about them.
+>
+> &mdash; [Alfred North Whitehead][cite], An Introduction to Mathematics (1911)
+
+
+API may stand for Application *Programming* Interface, but humans have to be able to read the APIs, too; someone has to do the programming. Django REST Framework supports generating human-friendly HTML output for each resource when the `HTML` format is requested. These pages allow for easy browsing of resources, as well as forms for submitting data to the resources using `POST`, `PUT`, and `DELETE`.
+
+## URLs
+
+If you include fully-qualified URLs in your resource output, they will be 'urlized' and made clickable for easy browsing by humans. The `rest_framework` package includes a [`reverse`][drfreverse] helper for this purpose.
+
+
+## Formats
+
+By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview].
+
+
+## Customizing
+
+To customize the look-and-feel, create a template called `api.html` and add it to your project, eg: `templates/rest_framework/api.html`, that extends the `rest_framework/base.html` template.
+
+The included browsable API template is built with [Bootstrap (2.1.1)][bootstrap], making it easy to customize the look-and-feel.
+
+### Theme
+
+To replace the theme wholesale, add a `bootstrap_theme` block to your `api.html` and insert a `link` to the desired Bootstrap theme css file. This will completely replace the included theme.
+
+ {% block bootstrap_theme %}
+ <link rel="stylesheet" href="/path/to/my/bootstrap.css" type="text/css">
+ {% endblock %}
+
+A suitable replacement theme can be generated using Bootstrap's [Customize Tool][bcustomize]. Also, there are pre-made themes available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above.
+
+You can also change the navbar variant, which by default is `navbar-inverse`, using the `bootstrap_navbar_variant` block. The empty `{% block bootstrap_navbar_variant %}{% endblock %}` will use the original Bootstrap navbar style.
+
+For more specific CSS tweaks, use the `extra_style` block instead.
+
+
+### Blocks
+
+All of the blocks available in the browsable API base template that can be used in your `api.html`.
+
+* `blockbots` - `<meta>` tag that blocks crawlers
+* `bodyclass` - (empty) class attribute for the `<body>`
+* `bootstrap_theme` - CSS for the Bootstrap theme
+* `bootstrap_navbar_variant` - CSS class for the navbar
+* `branding` - section of the navbar, see [Bootstrap components][bcomponentsnav]
+* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
+* `extrastyle` - (empty) extra CSS for the page
+* `extrahead` - (empty) extra markup for the page `<head>`
+* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned)
+* `global_heading` - (empty) Use to insert content below the header but before the breadcrumbs.
+* `title` - title of the page
+* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use {{ block.super }} to preserve the authentication links.
+
+#### Components
+
+All of the [Bootstrap components][bcomponents] are available.
+
+##### Tooltips
+
+The browsable API makes use of the Bootstrap tooltips component. Any element with the `js-tooltip` class and a `title` attribute has that title content displayed in a tooltip on hover after a 1000ms delay.
+
+
+### Advanced Customization
+
+#### Context
+
+The context that's available to the template:
+
+* `allowed_methods` : A list of methods allowed by the resource
+* `api_settings` : The API settings
+* `available_formats` : A list of formats allowed by the resource
+* `breadcrumblist` : The list of links following the chain of nested resources
+* `content` : The content of the API response
+* `description` : The description of the resource, generated from its docstring
+* `name` : The name of the resource
+* `post_form` : A form instance for use by the POST form (if allowed)
+* `put_form` : A form instance for use by the PUT form (if allowed)
+* `request` : The request object
+* `response` : The response object
+* `version` : The version of Django REST Framework
+* `view` : The view handling the request
+* `FORMAT_PARAM` : The view can accept a format override
+* `METHOD_PARAM` : The view can accept a method override
+
+#### Not using base.html
+
+For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your site, you can simply choose not to have `api.html` extend `base.html`. Then the page content and capabilities are entirely up to you.
+
+[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead
+[drfreverse]: ../api-guide/reverse.md
+[ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/
+[chromejsonview]: https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc
+[bootstrap]: http://getbootstrap.com
+[bcustomize]: http://twitter.github.com/bootstrap/customize.html#variables
+[bswatch]: http://bootswatch.com/
+[bcomponents]: http://twitter.github.com/bootstrap/components.html
+[bcomponentsnav]: http://twitter.github.com/bootstrap/components.html#navbar
+
diff --git a/docs/topics/browser-enhancements.md b/docs/topics/browser-enhancements.md
new file mode 100644
index 00000000..6a11f0fa
--- /dev/null
+++ b/docs/topics/browser-enhancements.md
@@ -0,0 +1,64 @@
+# Browser enhancements
+
+> "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"
+>
+> &mdash; [RESTful Web Services][cite], Leonard Richardson & Sam Ruby.
+
+## Browser based PUT, DELETE, etc...
+
+REST framework supports browser-based `PUT`, `DELETE` and other methods, by
+overloading `POST` requests using a hidden form field.
+
+Note that this is the same strategy as is used in [Ruby on Rails][rails].
+
+For example, given the following form:
+
+ <form action="/news-items/5" method="POST">
+ <input type="hidden" name="_method" value="DELETE">
+ </form>
+
+`request.method` would return `"DELETE"`.
+
+## Browser based submission of non-form content
+
+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:
+
+ <form action="/news-items/5" method="PUT">
+ <input type="hidden" name="_content_type" value="application/json">
+ <input name="_content" value="{'count': 1}">
+ </form>
+
+`request.content_type` would return `"application/json"`, and
+`request.stream` would return `"{'count': 1}"`
+
+## URL based accept headers
+
+REST framework can take `?accept=application/json` style URL parameters,
+which allow the `Accept` header to be overridden.
+
+This can be useful for testing the API from a web browser, where you don't
+have any control over what is sent in the `Accept` header.
+
+## URL based format suffixes
+
+REST framework can take `?format=json` style URL parameters, which can be a
+useful shortcut for determing which content type should be returned from
+the view.
+
+This is a more concise than using the `accept` override, but it also gives
+you less control. (For example you can't specify any media type parameters)
+
+## 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][html5]. There remains
+[ongoing discussion][put_delete] about adding support for `PUT` and `DELETE`,
+as well as how to support content types other than form-encoded data.
+
+[cite]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260
+[rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
+[html5]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24
+[put_delete]: http://amundsen.com/examples/put-delete-forms/
diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md
new file mode 100644
index 00000000..7fd61c10
--- /dev/null
+++ b/docs/topics/contributing.md
@@ -0,0 +1,15 @@
+# Contributing to REST framework
+
+> The world can only really be changed one piece at a time. The art is picking that piece.
+>
+> &mdash; [Tim Berners-Lee][cite]
+
+## Running the tests
+
+## Building the docs
+
+## Managing compatibility issues
+
+**Describe compat module**
+
+[cite]: http://www.w3.org/People/Berners-Lee/FAQ.html \ No newline at end of file
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
new file mode 100644
index 00000000..69d57802
--- /dev/null
+++ b/docs/topics/credits.md
@@ -0,0 +1,139 @@
+# Credits
+
+The following people have helped make REST framework great.
+
+* Tom Christie - [tomchristie]
+* Marko Tibold - [markotibold]
+* Paul Bagwell - [pbgwl]
+* Sébastien Piquemal - [sebpiq]
+* Carmen Wick - [cwick]
+* Alex Ehlke - [aehlke]
+* Alen Mujezinovic - [flashingpumpkin]
+* Carles Barrobés - [txels]
+* Michael Fötsch - [mfoetsch]
+* David Larlet - [david]
+* Andrew Straw - [astraw]
+* Zeth - [zeth]
+* Fernando Zunino - [fzunino]
+* Jens Alm - [ulmus]
+* Craig Blaszczyk - [jakul]
+* Garcia Solero - [garciasolero]
+* Tom Drummond - [devioustree]
+* Danilo Bargen - [gwrtheyrn]
+* Andrew McCloud - [amccloud]
+* Thomas Steinacher - [thomasst]
+* Meurig Freeman - [meurig]
+* Anthony Nemitz - [anemitz]
+* Ewoud Kohl van Wijngaarden - [ekohl]
+* Michael Ding - [yandy]
+* Mjumbe Poe - [mjumbewu]
+* Natim - [natim]
+* Sebastian Żurek - [sebzur]
+* Benoit C - [dzen]
+* Chris Pickett - [bunchesofdonald]
+* Ben Timby - [btimby]
+* Michele Lazzeri - [michelelazzeri-nextage]
+* Camille Harang - [mammique]
+* Paul Oswald - [poswald]
+* Sean C. Farley - [scfarley]
+* Daniel Izquierdo - [izquierdo]
+* Can Yavuz - [tschan]
+* Shawn Lewis - [shawnlewis]
+* Alec Perkins - [alecperkins]
+* Michael Barrett - [phobologic]
+* Mathieu Dhondt - [laundromat]
+* Johan Charpentier - [cyberj]
+* Jamie Matthews - [j4mie]
+* Mattbo - [mattbo]
+* Max Hurl - [maximilianhurl]
+* Tomi Pajunen - [eofs]
+* Rob Dobson - [rdobson]
+* Daniel Vaca Araujo - [diviei]
+* Madis Väin - [madisvain]
+* Stephan Groß - [minddust]
+
+Many thanks to everyone who's contributed to the project.
+
+## Additional thanks
+
+The documentation is built with [Bootstrap] and [Markdown].
+
+Project hosting is with [GitHub].
+
+Continuous integration testing is managed with [Travis CI][travis-ci].
+
+The [live sandbox][sandbox] is hosted on [Heroku].
+
+Various inspiration taken from the [Piston], [Tastypie] and [Dagny] projects.
+
+Development of REST framework 2.0 was sponsored by [DabApps].
+
+## Contact
+
+To contact the author directly:
+
+* twitter: [@_tomchristie][twitter]
+* email: [tom@tomchristie.com][email]
+
+[email]: mailto:tom@tomchristie.com
+[twitter]: http://twitter.com/_tomchristie
+[bootstrap]: http://twitter.github.com/bootstrap/
+[markdown]: http://daringfireball.net/projects/markdown/
+[github]: github.com/tomchristie/django-rest-framework
+[travis-ci]: https://secure.travis-ci.org/tomchristie/django-rest-framework
+[piston]: https://bitbucket.org/jespern/django-piston
+[tastypie]: https://github.com/toastdriven/django-tastypie
+[dagny]: https://github.com/zacharyvoase/dagny
+[dabapps]: http://lab.dabapps.com
+[sandbox]: http://restframework.herokuapp.com/
+[heroku]: http://www.heroku.com/
+
+[tomchristie]: https://github.com/tomchristie
+[markotibold]: https://github.com/markotibold
+[pbgwl]: https://github.com/pbgwl
+[sebpiq]: https://github.com/sebpiq
+[cwick]: https://github.com/cwick
+[aehlke]: https://github.com/aehlke
+[flashingpumpkin]: https://github.com/flashingpumpkin
+[txels]: https://github.com/txels
+[mfoetsch]: https://github.com/mfoetsch
+[david]: https://github.com/david
+[astraw]: https://github.com/astraw
+[zeth]: https://github.com/zeth
+[fzunino]: https://github.com/fzunino
+[ulmus]: https://github.com/ulmus
+[jakul]: https://github.com/jakul
+[garciasolero]: https://github.com/garciasolero
+[devioustree]: https://github.com/devioustree
+[gwrtheyrn]: https://github.com/gwrtheyrn
+[amccloud]: https://github.com/amccloud
+[thomasst]: https://github.com/thomasst
+[meurig]: https://github.com/meurig
+[anemitz]: https://github.com/anemitz
+[ekohl]: https://github.com/ekohl
+[yandy]: https://github.com/yandy
+[mjumbewu]: https://github.com/mjumbewu
+[natim]: https://github.com/natim
+[sebzur]: https://github.com/sebzur
+[dzen]: https://github.com/dzen
+[bunchesofdonald]: https://github.com/bunchesofdonald
+[btimby]: https://github.com/btimby
+[michelelazzeri-nextage]: https://github.com/michelelazzeri-nextage
+[mammique]: https://github.com/mammique
+[poswald]: https://github.com/poswald
+[scfarley]: https://github.com/scfarley
+[izquierdo]: https://github.com/izquierdo
+[tschan]: https://github.com/tschan
+[shawnlewis]: https://github.com/shawnlewis
+[alecperkins]: https://github.com/alecperkins
+[phobologic]: https://github.com/phobologic
+[laundromat]: https://github.com/laundromat
+[cyberj]: https://github.com/cyberj
+[j4mie]: https://github.com/j4mie
+[mattbo]: https://github.com/mattbo
+[maximilianhurl]: https://github.com/maximilianhurl
+[eofs]: https://github.com/eofs
+[rdobson]: https://github.com/rdobson
+[diviei]: https://github.com/diviei
+[madisvain]: https://github.com/madisvain
+[minddust]: https://github.com/minddust \ No newline at end of file
diff --git a/docs/topics/csrf.md b/docs/topics/csrf.md
new file mode 100644
index 00000000..043144c1
--- /dev/null
+++ b/docs/topics/csrf.md
@@ -0,0 +1,12 @@
+# Working with AJAX and CSRF
+
+> "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."
+>
+> &mdash; [Jeff Atwood][cite]
+
+* Explain need to add CSRF token to AJAX requests.
+* Explain deferred CSRF style used by REST framework
+* Why you should use Django's standard login/logout views, and not REST framework view
+
+
+[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
diff --git a/docs/topics/migration.md b/docs/topics/migration.md
new file mode 100644
index 00000000..25fc9074
--- /dev/null
+++ b/docs/topics/migration.md
@@ -0,0 +1,89 @@
+# 2.0 Migration Guide
+
+> Move fast and break things
+>
+> &mdash; Mark Zuckerberg, [the Hacker Way][cite].
+
+REST framework 2.0 introduces a radical redesign of the core components, and a large number of backwards breaking changes.
+
+### Serialization redesign.
+
+REST framework's serialization and deserialization previously used a slightly odd combination of serializers for output, and Django Forms and Model Forms for input. The serialization core has been completely redesigned based on work that was originally intended for Django core.
+
+2.0's form-like serializers comprehensively address those issues, and are a much more flexible and clean solution to the problems around accepting both form-based and non-form based inputs.
+
+### Generic views improved.
+
+When REST framework 0.1 was released the current Django version was 1.2. REST framework included a backport of the Django 1.3's upcoming `View` class, but it didn't take full advantage of the generic view implementations.
+
+As of 2.0 the generic views in REST framework tie in much more cleanly and obviously with Django's existing codebase, and the mixin architecture is radically simplified.
+
+### Cleaner request-response cycle.
+
+REST framework 2.0's request-response cycle is now much less complex.
+
+* Responses inherit from `SimpleTemplateResponse`, allowing rendering to be delegated to the response, not handled by the view.
+* Requests extend the regular `HttpRequest`, allowing authentication and parsing to be delegated to the request, not handled by the view.
+
+### Renamed attributes & classes.
+
+Various attributes and classes have been renamed in order to fit in better with Django's conventions.
+
+## Example: Blog Posts API
+
+Let's take a look at an example from the REST framework 0.4 documentation...
+
+ from djangorestframework.resources import ModelResource
+ from djangorestframework.reverse import reverse
+ from blogpost.models import BlogPost, Comment
+
+
+ class BlogPostResource(ModelResource):
+ """
+ A Blog Post has a *title* and *content*, and can be associated
+ with zero or more comments.
+ """
+ model = BlogPost
+ fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
+ ordering = ('-created',)
+
+ def url(self, instance):
+ return reverse('blog-post',
+ kwargs={'key': instance.key},
+ request=self.request)
+
+ def comments(self, instance):
+ return reverse('comments',
+ kwargs={'blogpost': instance.key},
+ request=self.request)
+
+
+ class CommentResource(ModelResource):
+ """
+ A Comment is associated with a given Blog Post and has a
+ *username* and *comment*, and optionally a *rating*.
+ """
+ model = Comment
+ fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
+ ordering = ('-created',)
+
+ def blogpost(self, instance):
+ return reverse('blog-post',
+ kwargs={'key': instance.blogpost.key},
+ request=self.request)
+
+There's a bit of a mix of concerns going on there. We've got some information about how the data should be serialized, such as the `fields` attribute, and some information about how it should be retrieved from the database - the `ordering` attribute.
+
+Let's start to re-write this for REST framework 2.0.
+
+ from rest_framework import serializers
+
+ class BlogPostSerializer(serializers.HyperlinkedModelSerializer):
+ model = BlogPost
+ fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
+
+ class CommentSerializer(serializers.HyperlinkedModelSerializer):
+ model = Comment
+ fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
+
+[cite]: http://www.wired.com/business/2012/02/zuck-letter/
diff --git a/CHANGELOG.rst b/docs/topics/release-notes.md
index dab69c6f..a466f4b1 100644
--- a/CHANGELOG.rst
+++ b/docs/topics/release-notes.md
@@ -1,8 +1,17 @@
-Release Notes
-=============
+# Release Notes
-0.4.0
------
+> Release Early, Release Often
+>
+> &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
+
+## 2.0.0
+
+* **Fix all of the things.** (Well, almost.)
+* For more information please see the [2.0 migration guide][migration].
+
+---
+
+## 0.4.0
* Supports Django 1.5.
* Fixes issues with 'HEAD' method.
@@ -14,8 +23,7 @@ Release Notes
* Improve setup (eg use staticfiles, not the defunct ADMIN_MEDIA_PREFIX)
* Sensible absolute URL generation, not using hacky set_script_prefix
-0.3.3
------
+## 0.3.3
* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
* Use `staticfiles` for css files.
@@ -30,8 +38,7 @@ Release Notes
* Bugfixes:
- Bug with PerUserThrottling when user contains unicode chars.
-0.3.2
------
+## 0.3.2
* Bugfixes:
* Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115)
@@ -43,44 +50,37 @@ Release Notes
* 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
------
+## 0.3.1
* [not documented]
-0.3.0
------
+## 0.3.0
* JSONP Support
* Bugfixes, including support for latest markdown release
-0.2.4
------
+## 0.2.4
* Fix broken IsAdminUser permission.
* OPTIONS support.
* XMLParser.
* Drop mentions of Blog, BitBucket.
-0.2.3
------
+## 0.2.3
* Fix some throttling bugs.
* ``X-Throttle`` header on throttling.
* Support for nesting resources on related models.
-0.2.2
------
+## 0.2.2
* Throttling support complete.
-0.2.1
------
+## 0.2.1
* Couple of simple bugfixes over 0.2.0
-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.
@@ -104,12 +104,13 @@ Release Notes
* 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
------
+## 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
------
+## 0.1.0
* Initial release.
+
+[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
+[migration]: migration.md \ No newline at end of file
diff --git a/docs/topics/rest-framework-2-announcement.md b/docs/topics/rest-framework-2-announcement.md
new file mode 100644
index 00000000..885d1918
--- /dev/null
+++ b/docs/topics/rest-framework-2-announcement.md
@@ -0,0 +1,100 @@
+# Django REST framework 2
+
+What it is, and why you should care.
+
+> Most people just make the mistake that it should be simple to design simple things. In reality, the effort required to design something is inversely proportional to the simplicity of the result.
+>
+> &mdash; [Roy Fielding][cite]
+
+---
+
+**Announcement:** REST framework 2 released - Tue 30th Oct 2012
+
+---
+
+REST framework 2 is an almost complete reworking of the original framework, which comprehensively addresses some of the original design issues.
+
+Because the latest version should be considered a re-release, rather than an incremental improvement, we've skipped a version, and called this release Django REST framework 2.0.
+
+This article is intended to give you a flavor of what REST framework 2 is, and why you might want to give it a try.
+
+## User feedback
+
+Before we get cracking, let's start with the hard sell, with a few bits of feedback from some early adopters…
+
+"Django REST framework 2 is beautiful. Some of the API design is worthy of @kennethreitz." - [Kit La Touche][quote1]
+
+"Since it's pretty much just Django, controlling things like URLs has been a breeze... I think [REST framework 2] has definitely got the right approach here; even simple things like being able to override a function called post to do custom work during rather than having to intimately know what happens during a post make a huge difference to your productivity." - [Ian Strachan][quote2]
+
+"I switched to the 2.0 branch and I don't regret it - fully refactored my code in another &half; day and it's *much* more to my tastes" - [Bruno Desthuilliers][quote3]
+
+Sounds good, right? Let's get into some details...
+
+## Serialization
+
+REST framework 2 includes a totally re-worked serialization engine, that was initially intended as a replacement for Django's existing inflexible fixture serialization, and which meets the following design goals:
+
+* A declarative serialization API, that mirrors Django's `Forms`/`ModelForms` API.
+* Structural concerns are decoupled from encoding concerns.
+* Able to support rendering and parsing to many formats, including both machine-readable representations and HTML forms.
+* Validation that can be mapped to obvious and comprehensive error responses.
+* Serializers that support both nested, flat, and partially-nested representations.
+* Relationships that can be expressed as primary keys, hyperlinks, slug fields, and other custom representations.
+
+Mapping between the internal state of the system and external representations of that state is the core concern of building Web APIs. Designing serializers that allow the developer to do so in a flexible and obvious way is a deceptively difficult design task, and with the new serialization API we think we've pretty much nailed it.
+
+## Generic views
+
+When REST framework was initially released at the start of 2011, the current Django release was version 1.2. REST framework included a backport of Django 1.3's upcoming `View` class, but it didn't take full advantage of the generic view implementations.
+
+With the new release the generic views in REST framework now tie in with Django's generic views. The end result is that framework is clean, lightweight and easy to use.
+
+## Requests, Responses & Views
+
+REST framework 2 includes `Request` and `Response` classes, than are used in place of Django's existing `HttpRequest` and `HttpResponse` classes. Doing so allows logic such as parsing the incoming request or rendering the outgoing response to be supported transparently by the framework.
+
+The `Request`/`Response` approach leads to a much cleaner API, less logic in the view itself, and a simple, obvious request-response cycle.
+
+REST framework 2 also allows you to work with both function-based and class-based views. For simple API views all you need is a single `@api_view` decorator, and you're good to go.
+
+
+## API Design
+
+Pretty much every aspect of REST framework has been reworked, with the aim of ironing out some of the design flaws of the previous versions. Each of the components of REST framework are cleanly decoupled, and can be used independantly of each-other, and there are no monolithic resource classes, overcomplicated mixin combinations, or opinionated serialization or URL routing decisions.
+
+## The Browseable API
+
+Django REST framework's most unique feature is the way it is able to serve up both machine-readable representations, and a fully browsable HTML representation to the same endpoints.
+
+Browseable Web APIs are easier to work with, visualize and debug, and generally makes it easier and more frictionless to inspect and work with.
+
+With REST framework 2, the browseable API gets a snazzy new bootstrap-based theme that looks great and is even nicer to work with.
+
+There are also some functionality improvments - actions such as as `POST` and `DELETE` will only display if the user has the appropriate permissions.
+
+![Browseable API][image]
+
+**Image above**: An example of the browseable API in REST framework 2
+
+## Documentation
+
+As you can see the documentation for REST framework has been radically improved. It gets a completely new style, using markdown for the documentation source, and a bootstrap-based theme for the styling.
+
+We're really pleased with how the docs style looks - it's simple and clean, is easy to navigate around, and we think it reads great.
+
+## Summary
+
+In short, we've engineered the hell outta this thing, and we're incredibly proud of the result.
+
+If you're interested please take a browse around the documentation. [The tutorial][tut] is a great place to get started.
+
+There's also a [live sandbox version of the tutorial API][sandbox] available for testing.
+
+[cite]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-724
+[quote1]: https://twitter.com/kobutsu/status/261689665952833536
+[quote2]: https://groups.google.com/d/msg/django-rest-framework/heRGHzG6BWQ/ooVURgpwVC0J
+[quote3]: https://groups.google.com/d/msg/django-rest-framework/flsXbvYqRoY/9lSyntOf5cUJ
+[image]: ../img/quickstart.png
+[readthedocs]: https://readthedocs.org/
+[tut]: ../tutorial/1-serialization.md
+[sandbox]: http://restframework.herokuapp.com/
diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md
new file mode 100644
index 00000000..d7646892
--- /dev/null
+++ b/docs/topics/rest-hypermedia-hateoas.md
@@ -0,0 +1,53 @@
+# REST, Hypermedia & HATEOAS
+
+> You keep using that word "REST". I do not think it means what you think it means.
+>
+> &mdash; Mike Amundsen, [REST fest 2012 keynote][cite].
+
+First off, the disclaimer. The name "Django REST framework" was chosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs".
+
+If you are serious about designing a Hypermedia APIs, you should look to resources outside of this documentation to help inform your design choices.
+
+The following fall into the "required reading" category.
+
+* Roy Fielding's dissertation - [Architectural Styles and
+the Design of Network-based Software Architectures][dissertation].
+* Roy Fielding's "[REST APIs must be hypertext-driven][hypertext-driven]" blog post.
+* Leonard Richardson & Sam Ruby's [RESTful Web Services][restful-web-services].
+* Mike Amundsen's [Building Hypermedia APIs with HTML5 and Node][building-hypermedia-apis].
+* Steve Klabnik's [Designing Hypermedia APIs][designing-hypermedia-apis].
+* The [Richardson Maturity Model][maturitymodel].
+
+For a more thorough background, check out Klabnik's [Hypermedia API reading list][readinglist].
+
+## Building Hypermedia APIs with REST framework
+
+REST framework is an agnostic Web API toolkit. It does help guide you towards building well-connected APIs, and makes it easy to design appropriate media types, but it does not strictly enforce any particular design style.
+
+## What REST framework provides.
+
+It is self evident that REST framework makes it possible to build Hypermedia APIs. The browseable API that it offers is built on HTML - the hypermedia language of the web.
+
+REST framework also includes [serialization] and [parser]/[renderer] components that make it easy to build appropriate media types, [hyperlinked relations][fields] for building well-connected systems, and great support for [content negotiation][conneg].
+
+## What REST framework doesn't provide.
+
+What REST framework doesn't do is give you is machine readable hypermedia formats such as [Collection+JSON][collection] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
+
+[cite]: http://vimeo.com/channels/restfest/page:2
+[dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
+[hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
+[restful-web-services]:
+[building-hypermedia-apis]: …
+[designing-hypermedia-apis]: http://designinghypermediaapis.com/
+[restisover]: http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over
+[readinglist]: http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list
+[maturitymodel]: http://martinfowler.com/articles/richardsonMaturityModel.html
+
+[collection]: http://www.amundsen.com/media-types/collection/
+[microformats]: http://microformats.org/wiki/Main_Page
+[serialization]: ../api-guide/serializers.md
+[parser]: ../api-guide/parsers.md
+[renderer]: ../api-guide/renderers.md
+[fields]: ../api-guide/fields.md
+[conneg]: ../api-guide/content-negotiation.md
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
new file mode 100644
index 00000000..5cf16a67
--- /dev/null
+++ b/docs/tutorial/1-serialization.md
@@ -0,0 +1,315 @@
+# Tutorial 1: Serialization
+
+## Introduction
+
+This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together.
+
+The tutorial is fairly in-depth, so you should probably get a cookie and a cup of your favorite brew before getting started.<!-- If you just want a quick overview, you should head over to the [quickstart] documentation instead. -->
+
+---
+
+**Note**: The final code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. There is also a sandbox version for testing, [available here][sandbox].
+
+---
+
+## Setting up a new environment
+
+Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is keep nicely isolated from any other projects we're working on.
+
+ :::bash
+ mkdir ~/env
+ virtualenv ~/env/tutorial
+ source ~/env/tutorial/bin/activate
+
+Now that we're inside a virtualenv environment, we can install our package requirements.
+
+ pip install django
+ pip install djangorestframework
+ pip install pygments # We'll be using this for the code highlighting
+
+**Note:** To exit the virtualenv environment at any time, just type `deactivate`. For more information see the [virtualenv documentation][virtualenv].
+
+## Getting started
+
+Okay, we're ready to get coding.
+To get started, let's create a new project to work with.
+
+ cd ~
+ 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.
+We're going to create a project that
+
+ python manage.py startapp snippets
+
+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 `snippets` app and the `rest_framework` app to `INSTALLED_APPS`.
+
+ INSTALLED_APPS = (
+ ...
+ 'rest_framework',
+ 'snippets'
+ )
+
+We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet views.
+
+ urlpatterns = patterns('',
+ url(r'^', include('snippets.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 `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets` app's `models.py` file.
+
+ from django.db import models
+ from pygments.lexers import get_all_lexers
+ from pygments.styles import get_all_styles
+
+ LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in get_all_lexers()])
+ STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles()))
+
+
+ class Snippet(models.Model):
+ created = models.DateTimeField(auto_now_add=True)
+ title = models.CharField(max_length=100, default='')
+ code = models.TextField()
+ linenos = models.BooleanField(default=False)
+ language = models.CharField(choices=LANGUAGE_CHOICES,
+ default='python',
+ max_length=100)
+ style = models.CharField(choices=STYLE_CHOICES,
+ default='friendly',
+ max_length=100)
+
+ class Meta:
+ ordering = ('created',)
+
+Don't forget to sync the database for the first time.
+
+ python manage.py syncdb
+
+## Creating a Serializer class
+
+The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similarly to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following.
+
+ from django.forms import widgets
+ from rest_framework import serializers
+ from snippets import models
+
+
+ class SnippetSerializer(serializers.Serializer):
+ pk = serializers.Field() # Note: `Field` is an untyped read-only field.
+ title = serializers.CharField(required=False,
+ max_length=100)
+ code = serializers.CharField(widget=widgets.Textarea,
+ max_length=100000)
+ linenos = serializers.BooleanField(required=False)
+ language = serializers.ChoiceField(choices=models.LANGUAGE_CHOICES,
+ default='python')
+ style = serializers.ChoiceField(choices=models.STYLE_CHOICES,
+ default='friendly')
+
+ def restore_object(self, attrs, instance=None):
+ """
+ Create or update a new snippet instance.
+ """
+ if instance:
+ # Update existing instance
+ instance.title = attrs['title']
+ instance.code = attrs['code']
+ instance.linenos = attrs['linenos']
+ instance.language = attrs['language']
+ instance.style = attrs['style']
+ return instance
+
+ # Create new instance
+ return models.Snippet(**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, let's create a code snippet to work with.
+
+ from snippets.models import Snippet
+ from snippets.serializers import SnippetSerializer
+ from rest_framework.renderers import JSONRenderer
+ from rest_framework.parsers import JSONParser
+
+ snippet = Snippet(code='print "hello, world"\n')
+ snippet.save()
+
+We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
+
+ serializer = SnippetSerializer(instance=snippet)
+ serializer.data
+ # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
+
+At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.
+
+ content = JSONRenderer().render(serializer.data)
+ content
+ # '{"pk": 1, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
+
+Deserialization is similar. First we parse a stream into python native datatypes...
+
+ import StringIO
+
+ stream = StringIO.StringIO(content)
+ data = JSONParser().parse(stream)
+
+...then we restore those native datatypes into to a fully populated object instance.
+
+ serializer = SnippetSerializer(data)
+ serializer.is_valid()
+ # True
+ serializer.object
+ # <Snippet: Snippet 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.
+
+## Using ModelSerializers
+
+Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep out code a bit more concise.
+
+In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes.
+
+Let's look at refactoring our serializer using the `ModelSerializer` class.
+Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class.
+
+ class SnippetSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Snippet
+ fields = ('pk', 'title', 'code', 'linenos', 'language', 'style')
+
+
+
+## Writing regular Django views using our Serializer
+
+Let's see how we can write some API views using our new Serializer class.
+For the moment we won't use any of REST framework's other features, we'll just write the views as regular Django views.
+
+We'll start off by creating a subclass of HttpResponse that we can use to render any data we return into `json`.
+
+Edit the `snippet/views.py` file, and add the following.
+
+ from django.http import HttpResponse
+ from django.views.decorators.csrf import csrf_exempt
+ from rest_framework.renderers import JSONRenderer
+ from rest_framework.parsers import JSONParser
+ from snippets.models import Snippet
+ from snippets.serializers import SnippetSerializer
+
+ 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 snippets, or creating a new snippet.
+
+ @csrf_exempt
+ def snippet_list(request):
+ """
+ List all code snippets, or create a new snippet.
+ """
+ if request.method == 'GET':
+ snippets = Snippet.objects.all()
+ serializer = SnippetSerializer(instance=snippets)
+ return JSONResponse(serializer.data)
+
+ elif request.method == 'POST':
+ data = JSONParser().parse(request)
+ serializer = SnippetSerializer(data)
+ if serializer.is_valid():
+ serializer.save()
+ return JSONResponse(serializer.data, status=201)
+ else:
+ return JSONResponse(serializer.errors, status=400)
+
+Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
+
+We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
+
+ @csrf_exempt
+ def snippet_detail(request, pk):
+ """
+ Retrieve, update or delete a code snippet.
+ """
+ try:
+ snippet = Snippet.objects.get(pk=pk)
+ except Snippet.DoesNotExist:
+ return HttpResponse(status=404)
+
+ if request.method == 'GET':
+ serializer = SnippetSerializer(instance=snippet)
+ return JSONResponse(serializer.data)
+
+ elif request.method == 'PUT':
+ data = JSONParser().parse(request)
+ serializer = SnippetSerializer(data, instance=snippet)
+ if serializer.is_valid():
+ serializer.save()
+ return JSONResponse(serializer.data)
+ else:
+ return JSONResponse(serializer.errors, status=400)
+
+ elif request.method == 'DELETE':
+ snippet.delete()
+ return HttpResponse(status=204)
+
+Finally we need to wire these views up. Create the `snippets/urls.py` file:
+
+ from django.conf.urls import patterns, url
+
+ urlpatterns = patterns('snippets.views',
+ url(r'^snippets/$', 'snippet_list'),
+ url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail')
+ )
+
+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][tut-2].
+
+[quickstart]: quickstart.md
+[repo]: https://github.com/tomchristie/rest-framework-tutorial
+[sandbox]: http://restframework.herokuapp.com/
+[virtualenv]: http://www.virtualenv.org/en/latest/index.html
+[tut-2]: 2-requests-and-responses.md
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
new file mode 100644
index 00000000..938739fa
--- /dev/null
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -0,0 +1,150 @@
+# Tutorial 2: Requests and Responses
+
+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 introduces 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 receive `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.
+
+We don't need our `JSONResponse` class anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly.
+
+ from rest_framework import status
+ from rest_framework.decorators import api_view
+ from rest_framework.response import Response
+ from snippet.models import Snippet
+ from snippet.serializers import SnippetSerializer
+
+
+ @api_view(['GET', 'POST'])
+ def snippet_list(request):
+ """
+ List all snippets, or create a new snippet.
+ """
+ if request.method == 'GET':
+ snippets = Snippet.objects.all()
+ serializer = SnippetSerializer(instance=snippets)
+ return Response(serializer.data)
+
+ elif request.method == 'POST':
+ serializer = SnippetSerializer(request.DATA)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ else:
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.
+
+ @api_view(['GET', 'PUT', 'DELETE'])
+ def snippet_detail(request, pk):
+ """
+ Retrieve, update or delete a snippet instance.
+ """
+ try:
+ snippet = Snippet.objects.get(pk=pk)
+ except Snippet.DoesNotExist:
+ return Response(status=status.HTTP_404_NOT_FOUND)
+
+ if request.method == 'GET':
+ serializer = SnippetSerializer(instance=snippet)
+ return Response(serializer.data)
+
+ elif request.method == 'PUT':
+ serializer = SnippetSerializer(request.DATA, instance=snippet)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data)
+ else:
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ elif request.method == 'DELETE':
+ snippet.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+This should all feel very familiar - there's not a lot different to working with 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 the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url].
+
+Start by adding a `format` keyword argument to both of the views, like so.
+
+ def snippet_list(request, format=None):
+
+and
+
+ def snippet_detail(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 django.conf.urls import patterns, url
+ from rest_framework.urlpatterns import format_suffix_patterns
+
+ urlpatterns = patterns('snippet.views',
+ url(r'^snippets/$', 'snippet_list'),
+ url(r'^snippets/(?P<pk>[0-9]+)$', 'snippet_detail')
+ )
+
+ 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 referring 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][tut-1]. 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/snippets/][devserver]."
+
+**Note: Right now the Browseable API only works with the CBV's. Need to fix that.**
+
+### Browsability
+
+Because the API chooses a return format based on what the client asks for, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a browser. This allows for the API to be easily browsable and usable by humans.
+
+See the [browsable api][browseable-api] topic for more information about the browsable API feature and how to customize it.
+
+
+## What's next?
+
+In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
+
+[json-url]: http://example.com/api/items/4.json
+[devserver]: http://127.0.0.1:8000/snippets/
+[browseable-api]: ../topics/browsable-api.md
+[tut-1]: 1-serialization.md
+[tut-3]: 3-class-based-views.md
diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
new file mode 100644
index 00000000..f27b5af0
--- /dev/null
+++ b/docs/tutorial/3-class-based-views.md
@@ -0,0 +1,150 @@
+# Tutorial 3: 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][dry].
+
+## 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 snippet.models import Snippet
+ from snippet.serializers import SnippetSerializer
+ from django.http import Http404
+ from rest_framework.views import APIView
+ from rest_framework.response import Response
+ from rest_framework import status
+
+
+ class SnippetList(APIView):
+ """
+ List all snippets, or create a new snippet.
+ """
+ def get(self, request, format=None):
+ snippets = Snippet.objects.all()
+ serializer = SnippetSerializer(instance=snippets)
+ return Response(serializer.data)
+
+ def post(self, request, format=None):
+ serializer = SnippetSerializer(request.DATA)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view.
+
+ class SnippetDetail(APIView):
+ """
+ Retrieve, update or delete a snippet instance.
+ """
+ def get_object(self, pk):
+ try:
+ return Snippet.objects.get(pk=pk)
+ except Snippet.DoesNotExist:
+ raise Http404
+
+ def get(self, request, pk, format=None):
+ snippet = self.get_object(pk)
+ serializer = SnippetSerializer(instance=snippet)
+ return Response(serializer.data)
+
+ def put(self, request, pk, format=None):
+ snippet = self.get_object(pk)
+ serializer = SnippetSerializer(request.DATA, instance=snippet)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def delete(self, request, pk, format=None):
+ snippet = self.get_object(pk)
+ snippet.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+That's looking good. Again, it's still pretty similar to the function based view right now.
+
+We'll also need to refactor our URLconf slightly now we're using class based views.
+
+ from django.conf.urls import patterns, url
+ from rest_framework.urlpatterns import format_suffix_patterns
+ from snippetpost import views
+
+ urlpatterns = patterns('',
+ url(r'^$', views.SnippetList.as_view()),
+ url(r'^(?P<pk>[0-9]+)$', views.SnippetDetail.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 are 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.
+
+Let's take a look at how we can compose our views by using the mixin classes.
+
+ from snippet.models import Snippet
+ from snippet.serializers import SnippetSerializer
+ from rest_framework import mixins
+ from rest_framework import generics
+
+ class SnippetList(mixins.ListModelMixin,
+ mixins.CreateModelMixin,
+ generics.MultipleObjectBaseView):
+ model = Snippet
+ serializer_class = SnippetSerializer
+
+ def get(self, request, *args, **kwargs):
+ return self.list(request, *args, **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ return self.create(request, *args, **kwargs)
+
+We'll take a moment to examine exactly what's happening here - We're building our view using `MultipleObjectBaseView`, and adding in `ListModelMixin` and `CreateModelMixin`.
+
+The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far.
+
+ class SnippetDetail(mixins.RetrieveModelMixin,
+ mixins.UpdateModelMixin,
+ mixins.DestroyModelMixin,
+ generics.SingleObjectBaseView):
+ model = Snippet
+ serializer_class = SnippetSerializer
+
+ def get(self, request, *args, **kwargs):
+ return self.retrieve(request, *args, **kwargs)
+
+ def put(self, request, *args, **kwargs):
+ return self.update(request, *args, **kwargs)
+
+ def delete(self, request, *args, **kwargs):
+ return self.destroy(request, *args, **kwargs)
+
+Pretty similar. This time we're using the `SingleObjectBaseView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
+
+## Using generic class based views
+
+Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use.
+
+ from snippet.models import Snippet
+ from snippet.serializers import SnippetSerializer
+ from rest_framework import generics
+
+
+ class SnippetList(generics.ListCreateAPIView):
+ model = Snippet
+ serializer_class = SnippetSerializer
+
+
+ class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
+ model = Snippet
+ serializer_class = SnippetSerializer
+
+Wow, that's pretty concise. We've got a huge amount for free, and our code looks like good, clean, idiomatic Django.
+
+Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can deal with authentication and permissions for our API.
+
+[dry]: http://en.wikipedia.org/wiki/Don't_repeat_yourself
+[tut-4]: 4-authentication-and-permissions.md
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
new file mode 100644
index 00000000..b0ed8f2a
--- /dev/null
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -0,0 +1,189 @@
+# Tutorial 4: Authentication & Permissions
+
+Currently our API doesn't have any restrictions on who can edit or delete code snippets. We'd like to have some more advanced behavior in order to make sure that:
+
+* Code snippets are always associated with a creator.
+* Only authenticated users may create snippets.
+* Only the creator of a snippet may update or delete it.
+* Unauthenticated requests should have full read-only access.
+
+## Adding information to our model
+
+We're going to make a couple of changes to our `Snippet` model class.
+First, let's add a couple of fields. One of those fields will be used to represent the user who created the code snippet. The other field will be used to store the highlighted HTML representation of the code.
+
+Add the following two fields to the model.
+
+ owner = models.ForeignKey('auth.User', related_name='snippets')
+ highlighted = models.TextField()
+
+We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code higlighting library.
+
+We'll need some extra imports:
+
+ from pygments.lexers import get_lexer_by_name
+ from pygments.formatters import HtmlFormatter
+ from pygments import highlight
+
+And now we can add a `.save()` method to our model class:
+
+ def save(self, *args, **kwargs):
+ """
+ Use the `pygments` library to create an highlighted HTML
+ representation of the code snippet.
+ """
+ lexer = get_lexer_by_name(self.language)
+ linenos = self.linenos and 'table' or False
+ options = self.title and {'title': self.title} or {}
+ formatter = HtmlFormatter(style=self.style, linenos=linenos,
+ full=True, **options)
+ self.highlighted = highlight(self.code, lexer, formatter)
+ super(Snippet, self).save(*args, **kwargs)
+
+When that's all done we'll need to update our database tables.
+Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
+
+ rm tmp.db
+ python ./manage.py syncdb
+
+You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the `createsuperuser` command.
+
+ python ./manage.py createsuperuser
+
+## Adding endpoints for our User models
+
+Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy:
+
+ class UserSerializer(serializers.ModelSerializer):
+ snippets = serializers.ManyPrimaryKeyRelatedField()
+
+ class Meta:
+ model = User
+ fields = ('pk', 'username', 'snippets')
+
+Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we've needed to add an explicit field for it.
+
+We'll also add a couple of views. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views.
+
+ class UserList(generics.ListAPIView):
+ model = User
+ serializer_class = UserSerializer
+
+
+ class UserInstance(generics.RetrieveAPIView):
+ model = User
+ serializer_class = UserSerializer
+
+Finally we need to add those views into the API, by referencing them from the URL conf.
+
+ url(r'^users/$', views.UserList.as_view()),
+ url(r'^users/(?P<pk>[0-9]+)/$', views.UserInstance.as_view())
+
+## Associating Snippets with Users
+
+Right now, if we created a code snippet, there'd be no way of associating the user that created the snippet, with the snippet instance. The user isn't sent as part of the serialized representation, but is instead a property of the incoming request.
+
+The way we deal with that is by overriding a `.pre_save()` method on our snippet views, that allows us to handle any information that is implicit in the incoming request or requested URL.
+
+On **both** the `SnippetList` and `SnippetInstance` view classes, add the following method:
+
+ def pre_save(self, obj):
+ obj.owner = self.request.user
+
+## Updating our serializer
+
+Now that snippets are associated with the user that created them, let's update our SnippetSerializer to reflect that.
+
+Add the following field to the serializer definition:
+
+ owner = serializers.Field(source='owner.username')
+
+**Note**: Make sure you also add `'owner',` to the list of fields in the inner `Meta` class.
+
+This field is doing something quite interesting. The `source` argument controls which attribute is used to populate a field, and can point at any attribute on the serialized instance. It can also take the dotted notation shown above, in which case it will traverse the given attributes, in a similar way as it is used with Django's template language.
+
+The field we've added is the untyped `Field` class, in contrast to the other typed fields, such as `CharField`, `BooleanField` etc... The untyped `Field` is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized.
+
+**TODO: Explain the SessionAuthentication and BasicAuthentication classes, and demonstrate using HTTP basic authentication with curl requests**
+
+## Adding required permissions to views
+
+Now that code snippets are associated with users we want to make sure that only authenticated users are able to create, update and delete code snippets.
+
+REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is `IsAuthenticatedOrReadOnly`, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access.
+
+Add the following property to **both** the `SnippetList` and `SnippetInstance` view classes.
+
+ permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
+
+**TODO: Now that the permissions are restricted, demonstrate using HTTP basic authentication with curl requests**
+
+## Adding login to the Browseable API
+
+If you open a browser and navigate to the browseable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user.
+
+We can add a login view for use with the browseable API, by editing our URLconf once more.
+
+Add the following import at the top of the file:
+
+ from django.conf.urls import include
+
+And, at the end of the file, add a pattern to include the login and logout views for the browseable API.
+
+ urlpatterns += patterns('',
+ url(r'^api-auth/', include('rest_framework.urls',
+ namespace='rest_framework'))
+ )
+
+The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the `'rest_framework'` namespace.
+
+Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earier, you'll be able to create code snippets again.
+
+Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet pks that are associated with each user, in each user's 'snippets' field.
+
+## Object level permissions
+
+Really we'd like all code snippets to be visible to anyone, but also make sure that only the user that created a code snippet is able update or delete it.
+
+To do that we're going to need to create a custom permission.
+
+In the snippets app, create a new file, `permissions.py`
+
+ from rest_framework import permissions
+
+
+ class IsOwnerOrReadOnly(permissions.BasePermission):
+ """
+ Custom permission to only allow owners of an object to edit it.
+ """
+
+ def has_permission(self, request, view, obj=None):
+ # Skip the check unless this is an object-level test
+ if obj is None:
+ return True
+
+ # Read permissions are allowed to any request
+ if request.method in permissions.SAFE_METHODS:
+ return True
+
+ # Write permissions are only allowed to the owner of the snippet
+ return obj.owner == request.user
+
+Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetInstance` class:
+
+ permission_classes = (permissions.IsAuthenticatedOrReadOnly,
+ IsOwnerOrReadOnly,)
+
+Make sure to also import the `IsOwnerOrReadOnly` class.
+
+ from snippets.permissions import IsOwnerOrReadOnly
+
+Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.
+
+## Summary
+
+We've now got a fairly fine-grained set of permissions on our Web API, and end points for users of the system and for the code snippets that they have created.
+
+In [part 5][tut-5] of the tutorial we'll look at how we can tie everything together by creating an HTML endpoint for our hightlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the system.
+
+[tut-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 00000000..1f663745
--- /dev/null
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -0,0 +1,173 @@
+# Tutorial 5 - Relationships & Hyperlinked APIs
+
+At the moment relationships within our API are represented by using primary keys. In this part of the tutorial we'll improve the cohesion and discoverability of our API, by instead using hyperlinking for relationships.
+
+## Creating an endpoint for the root of our API
+
+Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier.
+
+ from rest_framework import renderers
+ from rest_framework.decorators import api_view
+ from rest_framework.response import Response
+ from rest_framework.reverse import reverse
+
+
+ @api_view(('GET',))
+ def api_root(request, format=None):
+ return Response({
+ 'users': reverse('user-list', request=request),
+ 'snippets': reverse('snippet-list', request=request)
+ })
+
+Notice that we're using REST framework's `reverse` function in order to return fully-qualified URLs.
+
+## Creating an endpoint for the highlighted snippets
+
+The other obvious thing that's still missing from our pastebin API is the code highlighting endpoints.
+
+Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation. There are two style of HTML renderer provided by REST framework, one for dealing with HTML rendered using templates, the other for dealing with pre-rendered HTML. The second renderer is the one we'd like to use for this endpoint.
+
+The other thing we need to consider when creating the code highlight view is that there's no existing concreate generic view that we can use. We're not returning an object instance, but instead a property of an object instance.
+
+Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method.
+
+ class SnippetHighlight(generics.SingleObjectAPIView):
+ model = Snippet
+ renderer_classes = (renderers.StaticHTMLRenderer,)
+
+ def get(self, request, *args, **kwargs):
+ snippet = self.get_object()
+ return Response(snippet.highlighted)
+
+As usual we need to add the new views that we've created in to our URLconf.
+We'll add a url pattern for our new API root:
+
+ url(r'^$', 'api_root'),
+
+And then add a url pattern for the snippet highlights:
+
+ url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
+
+## Hyperlinking our API
+
+Dealing with relationships between entities is one of the more challenging aspects of Web API design. There are a number of different ways that we might choose to represent a relationship:
+
+* Using primary keys.
+* Using hyperlinking between entities.
+* Using a unique identifying slug field on the related entity.
+* Using the default string representation of the related entity.
+* Nesting the related entity inside the parent representation.
+* Some other custom representation.
+
+REST framework supports all of these styles, and can apply them across forward or reverse relationships, or apply them across custom managers such as generic foreign keys.
+
+In this case we'd like to use a hyperlinked style between entities. In order to do so, we'll modify our serializers to extend `HyperlinkedModelSerializer` instead of the existing `ModelSerializer`.
+
+The `HyperlinkedModelSerializer` has the following differences from `ModelSerializer`:
+
+* It does not include the `pk` field by default.
+* It includes a `url` field, using `HyperlinkedIdentityField`.
+* Relationships use `HyperlinkedRelatedField` and `ManyHyperlinkedRelatedField`,
+ instead of `PrimaryKeyRelatedField` and `ManyPrimaryKeyRelatedField`.
+
+We can easily re-write our existing serializers to use hyperlinking.
+
+ class SnippetSerializer(serializers.HyperlinkedModelSerializer):
+ owner = serializers.Field(source='owner.username')
+ highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
+
+ class Meta:
+ model = models.Snippet
+ fields = ('url', 'highlight', 'owner',
+ 'title', 'code', 'linenos', 'language', 'style')
+
+
+ class UserSerializer(serializers.HyperlinkedModelSerializer):
+ snippets = serializers.ManyHyperlinkedRelatedField(view_name='snippet-detail')
+
+ class Meta:
+ model = User
+ fields = ('url', 'username', 'snippets')
+
+Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.
+
+Because we've included format suffixed URLs such as `'.json'`, we also need to indicate on the `highlight` field that any format suffixed hyperlinks it returns should use the `'.html'` suffix.
+
+## Making sure our URL patterns are named
+
+If we're going to have a hyperlinked API, we need to make sure we name our URL patterns. Let's take a look at which URL patterns we need to name.
+
+* The root of our API refers to `'user-list'` and `'snippet-list'`.
+* Our snippet serializer includes a field that refers to `'snippet-highlight'`.
+* Our user serializer includes a field that refers to `'snippet-detail'`.
+* Our snippet and user serializers include `'url'` fields that by default will refer to `'{model_name}-detail'`, which in this case will be `'snippet-detail'` and `'user-detail'`.
+
+After adding all those names into our URLconf, our final `'urls.py'` file should look something like this:
+
+ # API endpoints
+ urlpatterns = format_suffix_patterns(patterns('snippets.views',
+ url(r'^$', 'api_root'),
+ url(r'^snippets/$',
+ views.SnippetList.as_view(),
+ name='snippet-list'),
+ url(r'^snippets/(?P<pk>[0-9]+)/$',
+ views.SnippetInstance.as_view(),
+ name='snippet-detail'),
+ url(r'^snippets/(?P<pk>[0-9]+)/highlight/$'
+ views.SnippetHighlight.as_view(),
+ name='snippet-highlight'),
+ url(r'^users/$',
+ views.UserList.as_view(),
+ name='user-list'),
+ url(r'^users/(?P<pk>[0-9]+)/$',
+ views.UserInstance.as_view(),
+ name='user-detail')
+ ))
+
+ # Login and logout views for the browsable API
+ urlpatterns += patterns('',
+ url(r'^api-auth/', include('rest_framework.urls',
+ namespace='rest_framework'))
+ )
+
+## Adding pagination
+
+The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages.
+
+We can change the default list style to use pagination, by modifying our `settings.py` file slightly. Add the following setting:
+
+ REST_FRAMEWORK = {
+ 'PAGINATE_BY': 10
+ }
+
+Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well seperated from your other project settings.
+
+We could also customize the pagination style if we needed too, but in this case we'll just stick with the default.
+
+## Reviewing our work
+
+If we open a browser and navigate to the browseable API, you'll find that you can now work your way around the API simply by following links.
+
+You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the hightlighted code HTML representations.
+
+We've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
+
+We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
+
+You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].
+
+## 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][github] by reviewing and subitting issues, and making pull requests.
+* Join the [REST framework discussion group][group], and help build the community.
+* Follow the author [on Twitter][twitter] and say hi.
+
+**Now go build some awesome things.**
+
+[repo]: https://github.com/tomchristie/rest-framework-tutorial
+[sandbox]: http://restframework.herokuapp.com/
+[github]: https://github.com/tomchristie/django-rest-framework
+[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
+[twitter]: https://twitter.com/_tomchristie \ No newline at end of file
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
new file mode 100644
index 00000000..6bde725b
--- /dev/null
+++ b/docs/tutorial/quickstart.md
@@ -0,0 +1,172 @@
+# Quickstart
+
+We're going to create a simple API to allow admin users to view and edit the users and groups in the system.
+
+Create a new Django project, and start a new app called `quickstart`. Once you've set up a database and got everything synced and ready to go open up the app's directory and we'll get coding...
+
+## Serializers
+
+First up we're going to define some serializers in `quickstart/serializers.py` that we'll use for our data representations.
+
+ from django.contrib.auth.models import User, Group
+ from rest_framework import serializers
+
+
+ class UserSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = User
+ fields = ('url', 'username', 'email', 'groups')
+
+
+ class GroupSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = Group
+ fields = ('url', 'name', 'permissions')
+
+Notice that we're using hyperlinked relations in this case, with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
+
+## Views
+
+Right, we'd better write some views then. Open `quickstart/views.py` and get typing.
+
+ from django.contrib.auth.models import User, Group
+ from rest_framework import generics
+ from rest_framework.decorators import api_view
+ from rest_framework.reverse import reverse
+ from rest_framework.response import Response
+ from quickstart.serializers import UserSerializer, GroupSerializer
+
+
+ @api_view(['GET'])
+ def api_root(request, format=None):
+ """
+ The entry endpoint of our API.
+ """
+ return Response({
+ 'users': reverse('user-list', request=request),
+ 'groups': reverse('group-list', request=request),
+ })
+
+
+ class UserList(generics.ListCreateAPIView):
+ """
+ API endpoint that represents a list of users.
+ """
+ model = User
+ serializer_class = UserSerializer
+
+
+ class UserDetail(generics.RetrieveUpdateDestroyAPIView):
+ """
+ API endpoint that represents a single user.
+ """
+ model = User
+ serializer_class = UserSerializer
+
+
+ class GroupList(generics.ListCreateAPIView):
+ """
+ API endpoint that represents a list of groups.
+ """
+ model = Group
+ serializer_class = GroupSerializer
+
+
+ class GroupDetail(generics.RetrieveUpdateDestroyAPIView):
+ """
+ API endpoint that represents a single group.
+ """
+ model = Group
+ serializer_class = GroupSerializer
+
+Let's take a moment to look at what we've done here before we move on. We have one function-based view representing the root of the API, and four class-based views which map to our database models, and specify which serializers should be used for representing that data. Pretty simple stuff.
+
+## URLs
+
+Okay, let's wire this baby up. On to `quickstart/urls.py`...
+
+ from django.conf.urls import patterns, url, include
+ from rest_framework.urlpatterns import format_suffix_patterns
+ from quickstart.views import UserList, UserDetail, GroupList, GroupDetail
+
+
+ urlpatterns = patterns('quickstart.views',
+ url(r'^$', 'api_root'),
+ url(r'^users/$', UserList.as_view(), name='user-list'),
+ url(r'^users/(?P<pk>\d+)/$', UserDetail.as_view(), name='user-detail'),
+ url(r'^groups/$', GroupList.as_view(), name='group-list'),
+ url(r'^groups/(?P<pk>\d+)/$', GroupDetail.as_view(), name='group-detail'),
+ )
+
+
+ # Format suffixes
+ urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'api'])
+
+
+ # Default login/logout views
+ urlpatterns += patterns('',
+ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
+ )
+
+There's a few things worth noting here.
+
+Firstly the names `user-detail` and `group-detail` are important. We're using the default hyperlinked relationships without explicitly specifying the view names, so we need to use names of the style `{modelname}-detail` to represent the model instance views.
+
+Secondly, we're modifying the urlpatterns using `format_suffix_patterns`, to append optional `.json` style suffixes to our URLs.
+
+Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browseable API.
+
+## Settings
+
+We'd also like to set a few global settings. We'd like to turn on pagination, and we want our API to only be accessible to admin users.
+
+ INSTALLED_APPS = (
+ ...
+ 'rest_framework',
+ )
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
+ 'PAGINATE_BY': 10
+ }
+
+Okay, that's us done.
+
+---
+
+## Testing our API
+
+We can now access our API, both from the command-line, using tools like `curl`...
+
+ bash: curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
+ {
+ "count": 2,
+ "next": null,
+ "previous": null,
+ "results": [
+ {
+ "email": "admin@example.com",
+ "groups": [],
+ "url": "http://127.0.0.1:8000/users/1/",
+ "username": "admin"
+ },
+ {
+ "email": "tom@example.com",
+ "groups": [],
+ "url": "http://127.0.0.1:8000/users/2/",
+ "username": "tom"
+ }
+ ]
+ }
+
+Or directly through the browser...
+
+![Quick start image][image]
+
+Great, that was easy!
+
+If you want to get a more in depth understanding of how REST framework fits together head on over to [the tutorial][tutorial], or start browsing the [API guide][guide].
+
+[image]: ../img/quickstart.png
+[tutorial]: 1-serialization.md
+[guide]: ../#api-guide
diff --git a/examples/.epio-app b/examples/.epio-app
deleted file mode 100644
index 47c4439d..00000000
--- a/examples/.epio-app
+++ /dev/null
@@ -1 +0,0 @@
-rest
diff --git a/examples/blogpost/models.py b/examples/blogpost/models.py
deleted file mode 100644
index 10732ab4..00000000
--- a/examples/blogpost/models.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from django.db import models
-from django.template.defaultfilters import slugify
-import uuid
-
-
-def uuid_str():
- return str(uuid.uuid1())
-
-
-RATING_CHOICES = ((0, 'Awful'),
- (1, 'Poor'),
- (2, 'OK'),
- (3, 'Good'),
- (4, 'Excellent'))
-
-MAX_POSTS = 10
-
-
-class BlogPost(models.Model):
- key = models.CharField(primary_key=True, max_length=64, default=uuid_str, editable=False)
- title = models.CharField(max_length=128)
- content = models.TextField()
- created = models.DateTimeField(auto_now_add=True)
- slug = models.SlugField(editable=False, default='')
-
- def save(self, *args, **kwargs):
- """
- For the purposes of the sandbox, limit the maximum number of stored models.
- """
- self.slug = slugify(self.title)
- super(self.__class__, self).save(*args, **kwargs)
- for obj in self.__class__.objects.order_by('-created')[MAX_POSTS:]:
- obj.delete()
-
-
-class Comment(models.Model):
- blogpost = models.ForeignKey(BlogPost, editable=False, related_name='comments')
- username = models.CharField(max_length=128)
- comment = models.TextField()
- rating = models.IntegerField(blank=True, null=True, choices=RATING_CHOICES, help_text='How did you rate this post?')
- created = models.DateTimeField(auto_now_add=True)
diff --git a/examples/blogpost/resources.py b/examples/blogpost/resources.py
deleted file mode 100644
index c3166bcf..00000000
--- a/examples/blogpost/resources.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from djangorestframework.resources import ModelResource
-from djangorestframework.reverse import reverse
-from blogpost.models import BlogPost, Comment
-
-
-class BlogPostResource(ModelResource):
- """
- A Blog Post has a *title* and *content*, and can be associated with zero or more comments.
- """
- model = BlogPost
- fields = ('created', 'title', 'slug', 'content', 'url', 'comments')
- ordering = ('-created',)
-
- def url(self, instance):
- return reverse('blog-post',
- kwargs={'key': instance.key},
- request=self.request)
-
- def comments(self, instance):
- return reverse('comments',
- kwargs={'blogpost': instance.key},
- request=self.request)
-
-
-class CommentResource(ModelResource):
- """
- A Comment is associated with a given Blog Post and has a *username* and *comment*, and optionally a *rating*.
- """
- model = Comment
- fields = ('username', 'comment', 'created', 'rating', 'url', 'blogpost')
- ordering = ('-created',)
-
- def blogpost(self, instance):
- return reverse('blog-post',
- kwargs={'key': instance.blogpost.key},
- request=self.request)
diff --git a/examples/blogpost/tests.py b/examples/blogpost/tests.py
deleted file mode 100644
index 23f1ac21..00000000
--- a/examples/blogpost/tests.py
+++ /dev/null
@@ -1,210 +0,0 @@
-"""Test a range of REST API usage of the example application.
-"""
-
-from django.test import TestCase
-from django.utils import simplejson as json
-
-from djangorestframework.compat import RequestFactory
-from djangorestframework.reverse import reverse
-from djangorestframework.views import InstanceModelView, ListOrCreateModelView
-
-from blogpost import models, urls
-#import blogpost
-
-
-# class AcceptHeaderTests(TestCase):
-# """Test correct behaviour of the Accept header as specified by RFC 2616:
-#
-# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"""
-#
-# def assert_accept_mimetype(self, mimetype, expect=None):
-# """Assert that a request with given mimetype in the accept header,
-# gives a response with the appropriate content-type."""
-# if expect is None:
-# expect = mimetype
-#
-# resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT=mimetype)
-#
-# self.assertEquals(resp['content-type'], expect)
-#
-#
-# def dont_test_accept_json(self):
-# """Ensure server responds with Content-Type of JSON when requested."""
-# self.assert_accept_mimetype('application/json')
-#
-# def dont_test_accept_xml(self):
-# """Ensure server responds with Content-Type of XML when requested."""
-# self.assert_accept_mimetype('application/xml')
-#
-# def dont_test_accept_json_when_prefered_to_xml(self):
-# """Ensure server responds with Content-Type of JSON when it is the client's prefered choice."""
-# self.assert_accept_mimetype('application/json;q=0.9, application/xml;q=0.1', expect='application/json')
-#
-# def dont_test_accept_xml_when_prefered_to_json(self):
-# """Ensure server responds with Content-Type of XML when it is the client's prefered choice."""
-# self.assert_accept_mimetype('application/json;q=0.1, application/xml;q=0.9', expect='application/xml')
-#
-# def dont_test_default_json_prefered(self):
-# """Ensure server responds with JSON in preference to XML."""
-# self.assert_accept_mimetype('application/json,application/xml', expect='application/json')
-#
-# def dont_test_accept_generic_subtype_format(self):
-# """Ensure server responds with an appropriate type, when the subtype is left generic."""
-# self.assert_accept_mimetype('text/*', expect='text/html')
-#
-# def dont_test_accept_generic_type_format(self):
-# """Ensure server responds with an appropriate type, when the type and subtype are left generic."""
-# self.assert_accept_mimetype('*/*', expect='application/json')
-#
-# def dont_test_invalid_accept_header_returns_406(self):
-# """Ensure server returns a 406 (not acceptable) response if we set the Accept header to junk."""
-# resp = self.client.get(reverse(views.RootResource), HTTP_ACCEPT='invalid/invalid')
-# self.assertNotEquals(resp['content-type'], 'invalid/invalid')
-# self.assertEquals(resp.status_code, 406)
-#
-# def dont_test_prefer_specific_over_generic(self): # This test is broken right now
-# """More specific accept types have precedence over less specific types."""
-# self.assert_accept_mimetype('application/xml, */*', expect='application/xml')
-# self.assert_accept_mimetype('*/*, application/xml', expect='application/xml')
-#
-#
-# class AllowedMethodsTests(TestCase):
-# """Basic tests to check that only allowed operations may be performed on a Resource"""
-#
-# def dont_test_reading_a_read_only_resource_is_allowed(self):
-# """GET requests on a read only resource should default to a 200 (OK) response"""
-# resp = self.client.get(reverse(views.RootResource))
-# self.assertEquals(resp.status_code, 200)
-#
-# def dont_test_writing_to_read_only_resource_is_not_allowed(self):
-# """PUT requests on a read only resource should default to a 405 (method not allowed) response"""
-# resp = self.client.put(reverse(views.RootResource), {})
-# self.assertEquals(resp.status_code, 405)
-#
-# def test_reading_write_only_not_allowed(self):
-# resp = self.client.get(reverse(views.WriteOnlyResource))
-# self.assertEquals(resp.status_code, 405)
-#
-# def test_writing_write_only_allowed(self):
-# resp = self.client.put(reverse(views.WriteOnlyResource), {})
-# self.assertEquals(resp.status_code, 200)
-#
-#
-#class EncodeDecodeTests(TestCase):
-# def setUp(self):
-# super(self.__class__, self).setUp()
-# self.input = {'a': 1, 'b': 'example'}
-#
-# def test_encode_form_decode_json(self):
-# content = self.input
-# resp = self.client.put(reverse(views.WriteOnlyResource), content)
-# output = json.loads(resp.content)
-# self.assertEquals(self.input, output)
-#
-# def test_encode_json_decode_json(self):
-# content = json.dumps(self.input)
-# resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json')
-# output = json.loads(resp.content)
-# self.assertEquals(self.input, output)
-#
-# #def test_encode_xml_decode_json(self):
-# # content = dict2xml(self.input)
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/json')
-# # output = json.loads(resp.content)
-# # self.assertEquals(self.input, output)
-#
-# #def test_encode_form_decode_xml(self):
-# # content = self.input
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, HTTP_ACCEPT='application/xml')
-# # output = xml2dict(resp.content)
-# # self.assertEquals(self.input, output)
-#
-# #def test_encode_json_decode_xml(self):
-# # content = json.dumps(self.input)
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
-# # output = xml2dict(resp.content)
-# # self.assertEquals(self.input, output)
-#
-# #def test_encode_xml_decode_xml(self):
-# # content = dict2xml(self.input)
-# # resp = self.client.put(reverse(views.WriteOnlyResource), content, 'application/json', HTTP_ACCEPT='application/xml')
-# # output = xml2dict(resp.content)
-# # self.assertEquals(self.input, output)
-#
-#class ModelTests(TestCase):
-# def test_create_container(self):
-# content = json.dumps({'name': 'example'})
-# resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json')
-# output = json.loads(resp.content)
-# self.assertEquals(resp.status_code, 201)
-# self.assertEquals(output['name'], 'example')
-# self.assertEquals(set(output.keys()), set(('absolute_uri', 'name', 'key')))
-#
-#class CreatedModelTests(TestCase):
-# def setUp(self):
-# content = json.dumps({'name': 'example'})
-# resp = self.client.post(reverse(views.ContainerFactory), content, 'application/json', HTTP_ACCEPT='application/json')
-# self.container = json.loads(resp.content)
-#
-# def test_read_container(self):
-# resp = self.client.get(self.container["absolute_uri"])
-# self.assertEquals(resp.status_code, 200)
-# container = json.loads(resp.content)
-# self.assertEquals(container, self.container)
-#
-# def test_delete_container(self):
-# resp = self.client.delete(self.container["absolute_uri"])
-# self.assertEquals(resp.status_code, 204)
-# self.assertEquals(resp.content, '')
-#
-# def test_update_container(self):
-# self.container['name'] = 'new'
-# content = json.dumps(self.container)
-# resp = self.client.put(self.container["absolute_uri"], content, 'application/json')
-# self.assertEquals(resp.status_code, 200)
-# container = json.loads(resp.content)
-# self.assertEquals(container, self.container)
-
-
-#above testcases need to probably moved to the core
-
-
-class TestRotation(TestCase):
- """For the example the maximum amount of Blogposts is capped off at views.MAX_POSTS.
- Whenever a new Blogpost is posted the oldest one should be popped."""
-
- def setUp(self):
- self.factory = RequestFactory()
- models.BlogPost.objects.all().delete()
-
- def test_get_to_root(self):
- '''Simple get to the *root* url of blogposts'''
- request = self.factory.get('/blog-post')
- view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
- response = view(request)
- self.assertEqual(response.status_code, 200)
-
- def test_blogposts_not_exceed_MAX_POSTS(self):
- '''Posting blog-posts should not result in more than MAX_POSTS items stored.'''
- for post in range(models.MAX_POSTS + 5):
- form_data = {'title': 'This is post #%s' % post, 'content': 'This is the content of post #%s' % post}
- request = self.factory.post('/blog-post', data=form_data)
- view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
- view(request)
- self.assertEquals(len(models.BlogPost.objects.all()),models.MAX_POSTS)
-
- def test_fifo_behaviour(self):
- '''It's fine that the Blogposts are capped off at MAX_POSTS. But we want to make sure we see FIFO behaviour.'''
- for post in range(15):
- form_data = {'title': '%s' % post, 'content': 'This is the content of post #%s' % post}
- request = self.factory.post('/blog-post', data=form_data)
- view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
- view(request)
- request = self.factory.get('/blog-post')
- view = ListOrCreateModelView.as_view(resource=urls.BlogPostResource)
- response = view(request)
- response_posts = json.loads(response.content)
- response_titles = [d['title'] for d in response_posts]
- response_titles.reverse()
- self.assertEquals(response_titles, ['%s' % i for i in range(models.MAX_POSTS - 5, models.MAX_POSTS + 5)])
-
diff --git a/examples/blogpost/urls.py b/examples/blogpost/urls.py
deleted file mode 100644
index e9bd2754..00000000
--- a/examples/blogpost/urls.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from djangorestframework.views import ListOrCreateModelView, InstanceModelView
-from blogpost.resources import BlogPostResource, CommentResource
-
-
-urlpatterns = patterns('',
- url(r'^$', ListOrCreateModelView.as_view(resource=BlogPostResource), name='blog-posts-root'),
- url(r'^(?P<key>[^/]+)/$', InstanceModelView.as_view(resource=BlogPostResource), name='blog-post'),
- url(r'^(?P<blogpost>[^/]+)/comments/$', ListOrCreateModelView.as_view(resource=CommentResource), name='comments'),
- url(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', InstanceModelView.as_view(resource=CommentResource)),
-)
diff --git a/examples/epio.ini b/examples/epio.ini
deleted file mode 100644
index 4e61a42d..00000000
--- a/examples/epio.ini
+++ /dev/null
@@ -1,62 +0,0 @@
-# This is an example epio.ini file.
-# We suggest you edit it to fit your application's needs.
-# Documentation for the options is available at www.ep.io/docs/epioini/
-
-[wsgi]
-
-# Location of your requirements file
-requirements = requirements-epio.txt
-
-
-[static]
-
-# Serve the static directory directly as /static
-/static/admin = ../shortcuts/django-admin-media/
-
-
-[services]
-
-# Uncomment to enable the PostgreSQL service.
-postgres = true
-
-# Uncomment to enable the Redis service
-# redis = true
-
-
-[checkout]
-
-# By default your code is put in a directory called 'app'.
-# You can change that here.
-# directory_name = my_project
-
-
-[env]
-
-# Set any additional environment variables here. For example:
-# IN_PRODUCTION = true
-
-
-[symlinks]
-
-# Any symlinks you'd like to add. As an example, link the symlink 'config.py'
-# to the real file 'configs/epio.py':
-# config.py = configs/epio.py
-
-media/ = %(data_directory)s/
-
-# #### If you're using Django, you'll want to uncomment some or all of these lines ####
-# [django]
-# # Path to your project root, relative to this directory.
-# base = .
-#
-# [static]
-# Serve the admin media
-# # Django 1.3
-# /static/admin = ../shortcuts/django-admin-media/
-# # Django 1.2 and below
-# /media = ../shortcuts/django-admin-media/
-#
-# [env]
-# # Use a different settings module for ep.io (i.e. with DEBUG=False)
-# DJANGO_SETTINGS_MODULE = production_settings
-
diff --git a/examples/manage.py b/examples/manage.py
deleted file mode 100755
index 5e78ea97..00000000
--- a/examples/manage.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env python
-from django.core.management import execute_manager
-try:
- import settings # Assumed to be in the same directory.
-except ImportError:
- import sys
- sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
- sys.exit(1)
-
-if __name__ == "__main__":
- execute_manager(settings)
diff --git a/examples/media/objectstore/.keep b/examples/media/objectstore/.keep
deleted file mode 100644
index 02be0b51..00000000
--- a/examples/media/objectstore/.keep
+++ /dev/null
@@ -1 +0,0 @@
-Force media/objectstore directory to created
diff --git a/examples/media/pygments/.keep b/examples/media/pygments/.keep
deleted file mode 100644
index 577b3f1b..00000000
--- a/examples/media/pygments/.keep
+++ /dev/null
@@ -1 +0,0 @@
-Force media/pygments directory to created
diff --git a/examples/mixin/urls.py b/examples/mixin/urls.py
deleted file mode 100644
index 900c532e..00000000
--- a/examples/mixin/urls.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3
-from djangorestframework.mixins import ResponseMixin
-from djangorestframework.renderers import DEFAULT_RENDERERS
-from djangorestframework.response import Response
-from djangorestframework.reverse import reverse
-
-from django.conf.urls.defaults import patterns, url
-
-
-class ExampleView(ResponseMixin, View):
- """An example view using Django 1.3's class based views.
- Uses djangorestframework's RendererMixin to provide support for multiple
- output formats."""
- renderers = DEFAULT_RENDERERS
-
- def get(self, request):
- url = reverse('mixin-view', request=request)
- response = Response(200, {'description': 'Some example content',
- 'url': url})
- return self.render(response)
-
-
-urlpatterns = patterns('',
- url(r'^$', ExampleView.as_view(), name='mixin-view'),
-)
diff --git a/examples/modelresourceexample/models.py b/examples/modelresourceexample/models.py
deleted file mode 100644
index 11f3eae2..00000000
--- a/examples/modelresourceexample/models.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django.db import models
-
-MAX_INSTANCES = 10
-
-
-class MyModel(models.Model):
- foo = models.BooleanField()
- bar = models.IntegerField(help_text='Must be an integer.')
- baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
- created = models.DateTimeField(auto_now_add=True)
-
- def save(self, *args, **kwargs):
- """
- For the purposes of the sandbox limit the maximum number of stored models.
- """
- super(MyModel, self).save(*args, **kwargs)
- while MyModel.objects.all().count() > MAX_INSTANCES:
- MyModel.objects.all().order_by('-created')[0].delete()
diff --git a/examples/modelresourceexample/resources.py b/examples/modelresourceexample/resources.py
deleted file mode 100644
index b74b0572..00000000
--- a/examples/modelresourceexample/resources.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from djangorestframework.resources import ModelResource
-from djangorestframework.reverse import reverse
-from modelresourceexample.models import MyModel
-
-
-class MyModelResource(ModelResource):
- model = MyModel
- fields = ('foo', 'bar', 'baz', 'url')
- ordering = ('created',)
-
- def url(self, instance):
- return reverse('model-resource-instance',
- kwargs={'id': instance.id},
- request=self.request)
diff --git a/examples/modelresourceexample/urls.py b/examples/modelresourceexample/urls.py
deleted file mode 100644
index c5e1f874..00000000
--- a/examples/modelresourceexample/urls.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from djangorestframework.views import ListOrCreateModelView, InstanceModelView
-from modelresourceexample.resources import MyModelResource
-
-my_model_list = ListOrCreateModelView.as_view(resource=MyModelResource)
-my_model_instance = InstanceModelView.as_view(resource=MyModelResource)
-
-urlpatterns = patterns('',
- url(r'^$', my_model_list, name='model-resource-root'),
- url(r'^(?P<id>[0-9]+)/$', my_model_instance, name='model-resource-instance'),
-)
diff --git a/examples/objectstore/__init__.py b/examples/objectstore/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/examples/objectstore/__init__.py
+++ /dev/null
diff --git a/examples/objectstore/urls.py b/examples/objectstore/urls.py
deleted file mode 100644
index 0a3effa7..00000000
--- a/examples/objectstore/urls.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from objectstore.views import ObjectStoreRoot, StoredObject
-
-urlpatterns = patterns('objectstore.views',
- url(r'^$', ObjectStoreRoot.as_view(), name='object-store-root'),
- url(r'^(?P<key>[A-Za-z0-9_-]{1,64})/$', StoredObject.as_view(), name='stored-object'),
-)
diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py
deleted file mode 100644
index 880bd3fc..00000000
--- a/examples/objectstore/views.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from django.conf import settings
-
-from djangorestframework.reverse import reverse
-from djangorestframework.views import View
-from djangorestframework.response import Response
-from djangorestframework import status
-
-import pickle
-import os
-import uuid
-import operator
-
-OBJECT_STORE_DIR = os.path.join(settings.MEDIA_ROOT, 'objectstore')
-MAX_FILES = 10
-
-if not os.path.exists(OBJECT_STORE_DIR):
- os.makedirs(OBJECT_STORE_DIR)
-
-
-def remove_oldest_files(dir, max_files):
- """
- Remove the oldest files in a directory 'dir', leaving at most 'max_files' remaining.
- We use this to limit the number of resources in the sandbox.
- """
- filepaths = [os.path.join(dir, file) for file in os.listdir(dir) if not file.startswith('.')]
- ctime_sorted_paths = [item[0] for item in sorted([(path, os.path.getctime(path)) for path in filepaths],
- key=operator.itemgetter(1), reverse=True)]
- [os.remove(path) for path in ctime_sorted_paths[max_files:]]
-
-
-def get_filename(key):
- """
- Given a stored object's key returns the file's path.
- """
- return os.path.join(OBJECT_STORE_DIR, key)
-
-
-def get_file_url(key, request):
- """
- Given a stored object's key returns the URL for the object.
- """
- return reverse('stored-object', kwargs={'key': key}, request=request)
-
-
-class ObjectStoreRoot(View):
- """
- Root of the Object Store API.
- Allows the client to get a complete list of all the stored objects, or to create a new stored object.
- """
-
- def get(self, request):
- """
- Return a list of all the stored object URLs. (Ordered by creation time, newest first)
- """
- filepaths = [os.path.join(OBJECT_STORE_DIR, file)
- for file in os.listdir(OBJECT_STORE_DIR)
- if not file.startswith('.')]
- ctime_sorted_basenames = [item[0] for item in sorted([(os.path.basename(path), os.path.getctime(path)) for path in filepaths],
- key=operator.itemgetter(1), reverse=True)]
- return [get_file_url(key, request) for key in ctime_sorted_basenames]
-
- def post(self, request):
- """
- Create a new stored object, with a unique key.
- """
- key = str(uuid.uuid1())
- filename = get_filename(key)
- pickle.dump(self.CONTENT, open(filename, 'wb'))
-
- remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES)
- url = get_file_url(key, request)
- return Response(status.HTTP_201_CREATED, self.CONTENT, {'Location': url})
-
-
-class StoredObject(View):
- """
- Represents a stored object.
- The object may be any picklable content.
- """
- def get(self, request, key):
- """
- Return a stored object, by unpickling the contents of a locally
- stored file.
- """
- filename = get_filename(key)
- if not os.path.exists(filename):
- return Response(status.HTTP_404_NOT_FOUND)
- return pickle.load(open(filename, 'rb'))
-
- def put(self, request, key):
- """
- Update/create a stored object, by pickling the request content to a
- locally stored file.
- """
- filename = get_filename(key)
- pickle.dump(self.CONTENT, open(filename, 'wb'))
- return self.CONTENT
-
- def delete(self, request, key):
- """
- Delete a stored object, by removing it's pickled file.
- """
- filename = get_filename(key)
- if not os.path.exists(filename):
- return Response(status.HTTP_404_NOT_FOUND)
- os.remove(filename)
diff --git a/examples/permissionsexample/__init__.py b/examples/permissionsexample/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/examples/permissionsexample/__init__.py
+++ /dev/null
diff --git a/examples/permissionsexample/fixtures/initial_data.json b/examples/permissionsexample/fixtures/initial_data.json
deleted file mode 100644
index 153de8e8..00000000
--- a/examples/permissionsexample/fixtures/initial_data.json
+++ /dev/null
@@ -1,18 +0,0 @@
-[
- {
- "pk": 2,
- "model": "auth.user",
- "fields": {
- "username": "test",
- "first_name": "",
- "last_name": "",
- "is_active": true,
- "is_superuser": false,
- "is_staff": false,
- "groups": [],
- "user_permissions": [],
- "password": "sha1$b3dff$671b4ab97f2714446da32670d27576614e176758",
- "email": ""
- }
- }
-]
diff --git a/examples/permissionsexample/models.py b/examples/permissionsexample/models.py
deleted file mode 100644
index 790bbaf8..00000000
--- a/examples/permissionsexample/models.py
+++ /dev/null
@@ -1 +0,0 @@
-#for fixture loading
diff --git a/examples/permissionsexample/urls.py b/examples/permissionsexample/urls.py
deleted file mode 100644
index 33cb9b5f..00000000
--- a/examples/permissionsexample/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from permissionsexample.views import PermissionsExampleView, ThrottlingExampleView, LoggedInExampleView
-
-urlpatterns = patterns('',
- url(r'^$', PermissionsExampleView.as_view(), name='permissions-example'),
- url(r'^throttling$', ThrottlingExampleView.as_view(), name='throttled-resource'),
- url(r'^loggedin$', LoggedInExampleView.as_view(), name='loggedin-resource'),
-)
diff --git a/examples/permissionsexample/views.py b/examples/permissionsexample/views.py
deleted file mode 100644
index 5aeae7eb..00000000
--- a/examples/permissionsexample/views.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from djangorestframework.views import View
-from djangorestframework.permissions import PerUserThrottling, IsAuthenticated
-from djangorestframework.reverse import reverse
-
-
-class PermissionsExampleView(View):
- """
- A container view for permissions examples.
- """
-
- def get(self, request):
- return [
- {
- 'name': 'Throttling Example',
- 'url': reverse('throttled-resource', request=request)
- },
- {
- 'name': 'Logged in example',
- 'url': reverse('loggedin-resource', request=request)
- },
- ]
-
-
-class ThrottlingExampleView(View):
- """
- A basic read-only View that has a **per-user throttle** of 10 requests per minute.
-
- If a user exceeds the 10 requests limit within a period of one minute, the
- throttle will be applied until 60 seconds have passed since the first request.
- """
-
- permissions = (PerUserThrottling,)
- throttle = '10/min'
-
- def get(self, request):
- """
- Handle GET requests.
- """
- return "Successful response to GET request because throttle is not yet active."
-
-
-class LoggedInExampleView(View):
- """
- You can login with **'test', 'test'.** or use curl:
-
- `curl -X GET -H 'Accept: application/json' -u test:test http://localhost:8000/permissions-example`
- """
-
- permissions = (IsAuthenticated, )
-
- def get(self, request):
- return 'You have permission to view this resource'
diff --git a/examples/pygments_api/__init__.py b/examples/pygments_api/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/examples/pygments_api/__init__.py
+++ /dev/null
diff --git a/examples/pygments_api/forms.py b/examples/pygments_api/forms.py
deleted file mode 100644
index 30a59a84..00000000
--- a/examples/pygments_api/forms.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from django import forms
-
-from pygments.lexers import get_all_lexers
-from pygments.styles import get_all_styles
-
-LEXER_CHOICES = sorted([(item[1][0], item[0]) for item in get_all_lexers()])
-STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles()))
-
-class PygmentsForm(forms.Form):
- """A simple form with some of the most important pygments settings.
- The code to be highlighted can be specified either in a text field, or by URL.
- We do some additional form validation to ensure clients see helpful error responses."""
-
- code = forms.CharField(widget=forms.Textarea,
- label='Code Text',
- max_length=1000000,
- help_text='(Copy and paste the code text here.)')
- title = forms.CharField(required=False,
- help_text='(Optional)',
- max_length=100)
- linenos = forms.BooleanField(label='Show Line Numbers',
- required=False)
- lexer = forms.ChoiceField(choices=LEXER_CHOICES,
- initial='python')
- style = forms.ChoiceField(choices=STYLE_CHOICES,
- initial='friendly')
-
-
diff --git a/examples/pygments_api/models.py b/examples/pygments_api/models.py
deleted file mode 100644
index 402a813e..00000000
--- a/examples/pygments_api/models.py
+++ /dev/null
@@ -1 +0,0 @@
-#We need models.py otherwise the test framework complains (http://code.djangoproject.com/ticket/7198)
diff --git a/examples/pygments_api/tests.py b/examples/pygments_api/tests.py
deleted file mode 100644
index 24726647..00000000
--- a/examples/pygments_api/tests.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from django.test import TestCase
-from django.utils import simplejson as json
-
-from djangorestframework.compat import RequestFactory
-
-from pygments_api import views
-import tempfile, shutil
-
-
-
-class TestPygmentsExample(TestCase):
-
- def setUp(self):
- self.factory = RequestFactory()
- self.temp_dir = tempfile.mkdtemp()
- views.HIGHLIGHTED_CODE_DIR = self.temp_dir
-
- def tearDown(self):
- try:
- shutil.rmtree(self.temp_dir)
- except Exception:
- pass
-
- def test_get_to_root(self):
- '''Just do a get on the base url'''
- request = self.factory.get('/pygments')
- view = views.PygmentsRoot.as_view()
- response = view(request)
- self.assertEqual(response.status_code, 200)
-
- def test_snippets_datetime_sorted(self):
- '''Pygments examples should be datetime sorted'''
- locations = []
- for snippet in 'abcdefghij': # String length must not exceed views.MAX_FILES, otherwise test fails
- form_data = {'code': '%s' % snippet, 'style':'friendly', 'lexer':'python'}
- request = self.factory.post('/pygments', data=form_data)
- view = views.PygmentsRoot.as_view()
- response = view(request)
- locations.append(response.items()[2][1])
- import time
- time.sleep(.1)
- request = self.factory.get('/pygments')
- view = views.PygmentsRoot.as_view()
- response = view(request)
- response_locations = json.loads(response.content)
- self.assertEquals(locations, response_locations)
-
-
-
diff --git a/examples/pygments_api/urls.py b/examples/pygments_api/urls.py
deleted file mode 100644
index e0d44ece..00000000
--- a/examples/pygments_api/urls.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from pygments_api.views import PygmentsRoot, PygmentsInstance
-
-urlpatterns = patterns('',
- url(r'^$', PygmentsRoot.as_view(), name='pygments-root'),
- url(r'^([a-zA-Z0-9-]+)/$', PygmentsInstance.as_view(), name='pygments-instance'),
-)
diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py
deleted file mode 100644
index f41fa739..00000000
--- a/examples/pygments_api/views.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from __future__ import with_statement # for python 2.5
-from django.conf import settings
-
-from djangorestframework.resources import FormResource
-from djangorestframework.response import Response
-from djangorestframework.renderers import BaseRenderer
-from djangorestframework.reverse import reverse
-from djangorestframework.views import View
-from djangorestframework import status
-
-from pygments.formatters import HtmlFormatter
-from pygments.lexers import get_lexer_by_name
-from pygments import highlight
-
-from forms import PygmentsForm
-
-import os
-import uuid
-import operator
-
-# We need somewhere to store the code snippets that we highlight
-HIGHLIGHTED_CODE_DIR = os.path.join(settings.MEDIA_ROOT, 'pygments')
-MAX_FILES = 10
-
-if not os.path.exists(HIGHLIGHTED_CODE_DIR):
- os.makedirs(HIGHLIGHTED_CODE_DIR)
-
-
-def list_dir_sorted_by_ctime(dir):
- """
- Return a list of files sorted by creation time
- """
- filepaths = [os.path.join(dir, file)
- for file in os.listdir(dir)
- if not file.startswith('.')]
- ctimes = [(path, os.path.getctime(path)) for path in filepaths]
- ctimes = sorted(ctimes, key=operator.itemgetter(1), reverse=False)
- return [filepath for filepath, ctime in ctimes]
-
-
-def remove_oldest_files(dir, max_files):
- """
- Remove the oldest files in a directory 'dir', leaving at most 'max_files' remaining.
- We use this to limit the number of resources in the sandbox.
- """
- [os.remove(path) for path in list_dir_sorted_by_ctime(dir)[max_files:]]
-
-
-class HTMLRenderer(BaseRenderer):
- """
- Basic renderer which just returns the content without any further serialization.
- """
- media_type = 'text/html'
-
-
-class PygmentsRoot(View):
- """
- This example demonstrates a simple RESTful Web API around the awesome pygments library.
- This top level resource is used to create highlighted code snippets, and to list all the existing code snippets.
- """
- form = PygmentsForm
-
- def get(self, request):
- """
- Return a list of all currently existing snippets.
- """
- unique_ids = [os.path.split(f)[1] for f in list_dir_sorted_by_ctime(HIGHLIGHTED_CODE_DIR)]
- return [reverse('pygments-instance', request=request, args=[unique_id]) for unique_id in unique_ids]
-
- def post(self, request):
- """
- Create a new highlighed snippet and return it's location.
- For the purposes of the sandbox example, also ensure we delete the oldest snippets if we have > MAX_FILES.
- """
- unique_id = str(uuid.uuid1())
- pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
-
- lexer = get_lexer_by_name(self.CONTENT['lexer'])
- linenos = 'table' if self.CONTENT['linenos'] else False
- options = {'title': self.CONTENT['title']} if self.CONTENT['title'] else {}
- formatter = HtmlFormatter(style=self.CONTENT['style'], linenos=linenos, full=True, **options)
-
- with open(pathname, 'w') as outfile:
- highlight(self.CONTENT['code'], lexer, formatter, outfile)
-
- remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
-
- return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', request=request, args=[unique_id])})
-
-
-class PygmentsInstance(View):
- """
- Simply return the stored highlighted HTML file with the correct mime type.
- This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class.
- """
- renderers = (HTMLRenderer,)
-
- def get(self, request, unique_id):
- """
- Return the highlighted snippet.
- """
- pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
- if not os.path.exists(pathname):
- return Response(status.HTTP_404_NOT_FOUND)
- return open(pathname, 'r').read()
-
- def delete(self, request, unique_id):
- """
- Delete the highlighted snippet.
- """
- pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
- if not os.path.exists(pathname):
- return Response(status.HTTP_404_NOT_FOUND)
- return os.remove(pathname)
-
diff --git a/examples/requirements-epio.txt b/examples/requirements-epio.txt
deleted file mode 100644
index b4962676..00000000
--- a/examples/requirements-epio.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Pygments==1.4
-Markdown==2.0.3
-git+git://github.com/tomchristie/django-rest-framework.git
diff --git a/examples/requirements.txt b/examples/requirements.txt
deleted file mode 100644
index 70371574..00000000
--- a/examples/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-# Pygments for the code highlighting example,
-# markdown for the docstring -> auto-documentation
-
-Pygments==1.4
-Markdown==2.0.3
-
-
diff --git a/examples/resourceexample/__init__.py b/examples/resourceexample/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/examples/resourceexample/__init__.py
+++ /dev/null
diff --git a/examples/resourceexample/forms.py b/examples/resourceexample/forms.py
deleted file mode 100644
index aa6e7685..00000000
--- a/examples/resourceexample/forms.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django import forms
-
-class MyForm(forms.Form):
- foo = forms.BooleanField(required=False)
- bar = forms.IntegerField(help_text='Must be an integer.')
- baz = forms.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
diff --git a/examples/resourceexample/urls.py b/examples/resourceexample/urls.py
deleted file mode 100644
index 6e141f3c..00000000
--- a/examples/resourceexample/urls.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.conf.urls.defaults import patterns, url
-from resourceexample.views import ExampleView, AnotherExampleView
-
-urlpatterns = patterns('',
- url(r'^$', ExampleView.as_view(), name='example-resource'),
- url(r'^(?P<num>[0-9]+)/$', AnotherExampleView.as_view(), name='another-example'),
-)
diff --git a/examples/resourceexample/views.py b/examples/resourceexample/views.py
deleted file mode 100644
index 0db23804..00000000
--- a/examples/resourceexample/views.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from djangorestframework.reverse import reverse
-from djangorestframework.views import View
-from djangorestframework.response import Response
-from djangorestframework import status
-
-from resourceexample.forms import MyForm
-
-
-class ExampleView(View):
- """
- A basic read-only view that points to 3 other views.
- """
-
- def get(self, request):
- """
- Handle GET requests, returning a list of URLs pointing to 3 other views.
- """
- resource_urls = [reverse('another-example',
- kwargs={'num': num},
- request=request)
- for num in range(3)]
- return {"Some other resources": resource_urls}
-
-
-class AnotherExampleView(View):
- """
- A basic view, that can be handle GET and POST requests.
- Applies some simple form validation on POST requests.
- """
- form = MyForm
-
- def get(self, request, num):
- """
- Handle GET requests.
- Returns a simple string indicating which view the GET request was for.
- """
- if int(num) > 2:
- return Response(status.HTTP_404_NOT_FOUND)
- return "GET request to AnotherExampleResource %s" % num
-
- def post(self, request, num):
- """
- Handle POST requests, with form validation.
- Returns a simple string indicating what content was supplied.
- """
- if int(num) > 2:
- return Response(status.HTTP_404_NOT_FOUND)
- return "POST request to AnotherExampleResource %s, with content: %s" % (num, repr(self.CONTENT))
diff --git a/examples/runtests.py b/examples/runtests.py
deleted file mode 100644
index a62d9a9a..00000000
--- a/examples/runtests.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import os
-import sys
-os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
-
-from django.conf import settings
-from django.test.utils import get_runner
-from coverage import coverage
-
-def main():
- """Run the tests for the examples and generate a coverage report."""
-
- # Discover the list of all modules that we should test coverage for
- project_dir = os.path.dirname(__file__)
- cov_files = []
- for (path, dirs, files) in os.walk(project_dir):
- # Drop tests and runtests directories from the test coverage report
- if os.path.basename(path) == 'tests' or os.path.basename(path) == 'runtests':
- continue
- cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')])
- TestRunner = get_runner(settings)
-
- cov = coverage()
- cov.erase()
- cov.start()
- if hasattr(TestRunner, 'func_name'):
- # Pre 1.2 test runners were just functions,
- # and did not support the 'failfast' option.
- import warnings
- warnings.warn(
- 'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
- DeprecationWarning
- )
- failures = TestRunner(None)
- else:
- test_runner = TestRunner()
- failures = test_runner.run_tests(['blogpost', 'pygments_api'])
-
- cov.stop()
- cov.report(cov_files)
- cov.xml_report(cov_files)
- sys.exit(failures)
-
-if __name__ == '__main__':
- main()
diff --git a/examples/sandbox/__init__.py b/examples/sandbox/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/examples/sandbox/__init__.py
+++ /dev/null
diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py
deleted file mode 100644
index 11f68945..00000000
--- a/examples/sandbox/views.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""The root view for the examples provided with Django REST framework"""
-
-from djangorestframework.reverse import reverse
-from djangorestframework.views import View
-
-
-class Sandbox(View):
- """This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org).
-
- These examples are provided to help you get a better idea of some of the features of RESTful APIs created using the framework.
-
- All the example APIs allow anonymous access, and can be navigated either through the browser or from the command line...
-
- bash: curl -X GET http://shielded-mountain-6732.herokuapp.com/ # (Use default renderer)
- bash: curl -X GET http://shielded-mountain-6732.herokuapp.com/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
-
- The examples provided:
-
- 1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class.
- 2. A basic example using the [ModelResource](http://django-rest-framework.org/library/resource.html#resources.ModelResource) class.
- 3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [RendererMixin](http://django-rest-framework.org/library/renderers.html).
- 4. A generic object store API.
- 5. A code highlighting API.
- 6. A blog posts and comments API.
- 7. A basic example using permissions.
-
- Please feel free to browse, create, edit and delete the resources in these examples."""
-
- def get(self, request):
- return [{'name': 'Simple Resource example', 'url': reverse('example-resource', request=request)},
- {'name': 'Simple ModelResource example', 'url': reverse('model-resource-root', request=request)},
- {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view', request=request)},
- {'name': 'Object store API', 'url': reverse('object-store-root', request=request)},
- {'name': 'Code highlighting API', 'url': reverse('pygments-root', request=request)},
- {'name': 'Blog posts API', 'url': reverse('blog-posts-root', request=request)},
- {'name': 'Permissions example', 'url': reverse('permissions-example', request=request)}
- ]
diff --git a/examples/settings.py b/examples/settings.py
deleted file mode 100644
index 5ff9fd0f..00000000
--- a/examples/settings.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Settings for djangorestframework examples project
-import django
-import os
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-ADMINS = (
- # ('Your Name', 'your_email@domain.com'),
-)
-
-MANAGERS = ADMINS
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
- 'NAME': 'sqlite3.db', # Or path to database file if using sqlite3.
- 'USER': '', # Not used with sqlite3.
- 'PASSWORD': '', # Not used with sqlite3.
- 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
- 'PORT': '', # Set to empty string for default. Not used with sqlite3.
- }
-}
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# On Unix systems, a value of None will cause Django to use the same
-# timezone as the operating system.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = 'Europe/London'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en-uk'
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
-USE_L10N = True
-
-# Absolute filesystem path to the directory that will hold user-uploaded files.
-# Example: "/home/media/media.lawrence.com/"
-# NOTE: Some of the djangorestframework examples use MEDIA_ROOT to store content.
-MEDIA_ROOT = os.path.join(os.getenv('EPIO_DATA_DIRECTORY', '.'), 'media')
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash if there is a path component (optional in other cases).
-# Examples: "http://media.lawrence.com", "http://example.com/media/"
-# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL.
-MEDIA_URL = '/uploads/'
-
-STATIC_URL = '/static/'
-
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
-# 'django.template.loaders.eggs.Loader',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
-)
-
-ROOT_URLCONF = 'urls'
-
-TEMPLATE_DIRS = (
- # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
- # Always use forward slashes, even on Windows.
- # Don't forget to use absolute paths, not relative paths.
-)
-
-if django.VERSION < (1, 3):
- staticfiles = 'staticfiles'
-else:
- staticfiles = 'django.contrib.staticfiles'
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- staticfiles,
- 'django.contrib.messages',
-
- 'djangorestframework',
-
- 'resourceexample',
- 'modelresourceexample',
- 'objectstore',
- 'pygments_api',
- 'blogpost',
- 'permissionsexample',
-)
-
-import os
-if os.environ.get('HUDSON_URL', None):
- TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
- TEST_OUTPUT_VERBOSE = True
- TEST_OUTPUT_DESCRIPTIONS = True
- TEST_OUTPUT_DIR = 'xmlrunner'
diff --git a/examples/urls.py b/examples/urls.py
deleted file mode 100644
index fda7942f..00000000
--- a/examples/urls.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from django.conf.urls.defaults import patterns, include, url
-from sandbox.views import Sandbox
-try:
- from django.contrib.staticfiles.urls import staticfiles_urlpatterns
-except ImportError: # Django <= 1.2
- from staticfiles.urls import staticfiles_urlpatterns
-
-
-urlpatterns = patterns('',
- (r'^$', Sandbox.as_view()),
- (r'^resource-example/', include('resourceexample.urls')),
- (r'^model-resource-example/', include('modelresourceexample.urls')),
- (r'^mixin/', include('mixin.urls')),
- (r'^object-store/', include('objectstore.urls')),
- (r'^pygments/', include('pygments_api.urls')),
- (r'^blog-post/', include('blogpost.urls')),
- (r'^permissions-example/', include('permissionsexample.urls')),
- url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
-)
-
-urlpatterns += staticfiles_urlpatterns()
diff --git a/mkdocs.py b/mkdocs.py
new file mode 100755
index 00000000..3f6c5f65
--- /dev/null
+++ b/mkdocs.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+import markdown
+import os
+import re
+import shutil
+import sys
+
+root_dir = os.path.abspath(os.path.dirname(__file__))
+docs_dir = os.path.join(root_dir, 'docs')
+html_dir = os.path.join(root_dir, 'html')
+
+local = not '--deploy' in sys.argv
+
+if local:
+ base_url = 'file://%s/' % os.path.normpath(os.path.join(os.getcwd(), html_dir))
+ suffix = '.html'
+ index = 'index.html'
+else:
+ base_url = 'http://django-rest-framework.org'
+ suffix = ''
+ index = ''
+
+
+main_header = '<li class="main"><a href="#{{ anchor }}">{{ title }}</a></li>'
+sub_header = '<li><a href="#{{ anchor }}">{{ title }}</a></li>'
+code_label = r'<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/\1"><span class="label label-info">\1</span></a>'
+
+page = open(os.path.join(docs_dir, 'template.html'), 'r').read()
+
+# Copy static files
+# for static in ['css', 'js', 'img']:
+# source = os.path.join(docs_dir, 'static', static)
+# target = os.path.join(html_dir, static)
+# if os.path.exists(target):
+# shutil.rmtree(target)
+# shutil.copytree(source, target)
+
+for (dirpath, dirnames, filenames) in os.walk(docs_dir):
+ relative_dir = dirpath.replace(docs_dir, '').lstrip(os.path.sep)
+ build_dir = os.path.join(html_dir, relative_dir)
+
+ if not os.path.exists(build_dir):
+ os.makedirs(build_dir)
+
+ for filename in filenames:
+ path = os.path.join(dirpath, filename)
+
+ if not filename.endswith('.md'):
+ if relative_dir:
+ output_path = os.path.join(build_dir, filename)
+ shutil.copy(path, output_path)
+ continue
+
+ output_path = os.path.join(build_dir, filename[:-3] + '.html')
+
+ toc = ''
+ text = open(path, 'r').read().decode('utf-8')
+ for line in text.splitlines():
+ if line.startswith('# '):
+ title = line[2:].strip()
+ template = main_header
+ elif line.startswith('## '):
+ title = line[3:].strip()
+ template = sub_header
+ else:
+ continue
+
+ anchor = title.lower().replace(' ', '-').replace(':-', '-').replace("'", '').replace('?', '').replace('.', '')
+ template = template.replace('{{ title }}', title)
+ template = template.replace('{{ anchor }}', anchor)
+ toc += template + '\n'
+
+ content = markdown.markdown(text, ['headerid'])
+
+ output = page.replace('{{ content }}', content).replace('{{ toc }}', toc).replace('{{ base_url }}', base_url).replace('{{ suffix }}', suffix).replace('{{ index }}', index)
+ output = output.replace('{{ page_id }}', filename[:-3])
+ output = re.sub(r'a href="([^"]*)\.md"', r'a href="\1%s"' % suffix, output)
+ output = re.sub(r'<pre><code>:::bash', r'<pre class="prettyprint lang-bsh">', output)
+ output = re.sub(r'<pre>', r'<pre class="prettyprint lang-py">', output)
+ output = re.sub(r'<a class="github" href="([^"]*)"></a>', code_label, output)
+ open(output_path, 'w').write(output.encode('utf-8'))
diff --git a/optionals.txt b/optionals.txt
new file mode 100644
index 00000000..cf0d8da4
--- /dev/null
+++ b/optionals.txt
@@ -0,0 +1,2 @@
+markdown>=2.1.0
+PyYAML>=3.10
diff --git a/requirements.txt b/requirements.txt
index 56926c0f..730c1d07 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1 @@
-# 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
diff --git a/djangorestframework/__init__.py b/rest_framework/__init__.py
index ee050f0d..557f5943 100644
--- a/djangorestframework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '0.4.0'
+__version__ = '2.0.0'
VERSION = __version__ # synonym
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
new file mode 100644
index 00000000..30c78ebc
--- /dev/null
+++ b/rest_framework/authentication.py
@@ -0,0 +1,125 @@
+"""
+Provides a set of pluggable authentication policies.
+"""
+
+from django.contrib.auth import authenticate
+from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError
+from rest_framework import exceptions
+from rest_framework.compat import CsrfViewMiddleware
+from rest_framework.authtoken.models import Token
+import base64
+
+
+class BaseAuthentication(object):
+ """
+ All authentication classes should extend BaseAuthentication.
+ """
+
+ def authenticate(self, request):
+ """
+ Authenticate the request and return a two-tuple of (user, token).
+ """
+ raise NotImplementedError(".authenticate() must be overridden.")
+
+
+class BasicAuthentication(BaseAuthentication):
+ """
+ HTTP Basic authentication against username/password.
+ """
+
+ def authenticate(self, request):
+ """
+ Returns a `User` if a correct username and password have been supplied
+ using HTTP Basic authentication. Otherwise returns `None`.
+ """
+ if 'HTTP_AUTHORIZATION' in request.META:
+ auth = request.META['HTTP_AUTHORIZATION'].split()
+ if len(auth) == 2 and auth[0].lower() == "basic":
+ try:
+ auth_parts = base64.b64decode(auth[1]).partition(':')
+ except TypeError:
+ return None
+
+ try:
+ userid = smart_unicode(auth_parts[0])
+ password = smart_unicode(auth_parts[2])
+ except DjangoUnicodeDecodeError:
+ return None
+
+ return self.authenticate_credentials(userid, password)
+
+ def authenticate_credentials(self, userid, password):
+ """
+ Authenticate the userid and password against username and password.
+ """
+ user = authenticate(username=userid, password=password)
+ if user is not None and user.is_active:
+ return (user, None)
+
+
+class SessionAuthentication(BaseAuthentication):
+ """
+ Use Django's session framework for authentication.
+ """
+
+ def authenticate(self, request):
+ """
+ Returns a `User` if the request session currently has a logged in user.
+ Otherwise returns `None`.
+ """
+
+ # Get the underlying HttpRequest object
+ http_request = request._request
+ user = getattr(http_request, 'user', None)
+
+ # Unauthenticated, CSRF validation not required
+ if not user or not user.is_active:
+ return
+
+ # Enforce CSRF validation for session based authentication.
+ class CSRFCheck(CsrfViewMiddleware):
+ def _reject(self, request, reason):
+ # Return the failure reason instead of an HttpResponse
+ return reason
+
+ reason = CSRFCheck().process_view(http_request, None, (), {})
+ if reason:
+ # CSRF failed, bail with explicit error message
+ raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
+
+ # CSRF passed with authenticated user
+ return (user, None)
+
+
+class TokenAuthentication(BaseAuthentication):
+ """
+ Simple token based authentication.
+
+ Clients should authenticate by passing the token key in the "Authorization"
+ HTTP header, prepended with the string "Token ". For example:
+
+ Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
+ """
+
+ model = Token
+ """
+ A custom token model may be used, but must have the following properties.
+
+ * key -- The string identifying the token
+ * user -- The user to which the token belongs
+ """
+
+ def authenticate(self, request):
+ auth = request.META.get('HTTP_AUTHORIZATION', '').split()
+
+ if len(auth) == 2 and auth[0].lower() == "token":
+ key = auth[1]
+ try:
+ token = self.model.objects.get(key=key)
+ except self.model.DoesNotExist:
+ return None
+
+ if token.user.is_active:
+ return (token.user, token)
+
+# TODO: OAuthAuthentication
diff --git a/djangorestframework/runtests/__init__.py b/rest_framework/authtoken/__init__.py
index e69de29b..e69de29b 100644
--- a/djangorestframework/runtests/__init__.py
+++ b/rest_framework/authtoken/__init__.py
diff --git a/rest_framework/authtoken/migrations/0001_initial.py b/rest_framework/authtoken/migrations/0001_initial.py
new file mode 100644
index 00000000..9d750381
--- /dev/null
+++ b/rest_framework/authtoken/migrations/0001_initial.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Token'
+ db.create_table('authtoken_token', (
+ ('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)),
+ ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='auth_token', unique=True, to=orm['auth.User'])),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ))
+ db.send_create_signal('authtoken', ['Token'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'Token'
+ db.delete_table('authtoken_token')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'authtoken.token': {
+ 'Meta': {'object_name': 'Token'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'auth_token'", 'unique': 'True', 'to': "orm['auth.User']"})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['authtoken']
diff --git a/djangorestframework/templatetags/__init__.py b/rest_framework/authtoken/migrations/__init__.py
index e69de29b..e69de29b 100644
--- a/djangorestframework/templatetags/__init__.py
+++ b/rest_framework/authtoken/migrations/__init__.py
diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py
new file mode 100644
index 00000000..5b3071aa
--- /dev/null
+++ b/rest_framework/authtoken/models.py
@@ -0,0 +1,25 @@
+import uuid
+import hmac
+from hashlib import sha1
+from django.db import models
+
+
+class Token(models.Model):
+ """
+ The default authorization token model.
+ """
+ key = models.CharField(max_length=40, primary_key=True)
+ user = models.OneToOneField('auth.User', related_name='auth_token')
+ created = models.DateTimeField(auto_now_add=True)
+
+ def save(self, *args, **kwargs):
+ if not self.key:
+ self.key = self.generate_key()
+ return super(Token, self).save(*args, **kwargs)
+
+ def generate_key(self):
+ unique = str(uuid.uuid4())
+ return hmac.new(unique, digestmod=sha1).hexdigest()
+
+ def __unicode__(self):
+ return self.key
diff --git a/examples/__init__.py b/rest_framework/authtoken/views.py
index e69de29b..e69de29b 100644
--- a/examples/__init__.py
+++ b/rest_framework/authtoken/views.py
diff --git a/djangorestframework/compat.py b/rest_framework/compat.py
index 0772e17b..b0367a32 100644
--- a/djangorestframework/compat.py
+++ b/rest_framework/compat.py
@@ -1,6 +1,8 @@
"""
-The :mod:`compat` module provides support for backwards compatibility with older versions of django/python.
+The `compat` module provides support for backwards compatibility with older
+versions of django/python, and compatbility wrappers around optional packages.
"""
+# flake8: noqa
import django
# cStringIO only if it's available, otherwise StringIO
@@ -10,131 +12,25 @@ except ImportError:
import StringIO
-# parse_qs from 'urlparse' module unless python 2.5, in which case from 'cgi'
-try:
- # python >= 2.6
- from urlparse import parse_qs
-except ImportError:
- # python < 2.6
- from cgi import parse_qs
-
-
-# django.test.client.RequestFactory (Required for Django < 1.3)
-try:
- from django.test.client import RequestFactory
-except ImportError:
- from django.test import Client
- from django.core.handlers.wsgi import WSGIRequest
-
- # From: http://djangosnippets.org/snippets/963/
- # Lovely stuff
- class RequestFactory(Client):
- """
- Class that lets you create mock :obj:`Request` objects for use in testing.
-
- Usage::
-
- rf = RequestFactory()
- get_request = rf.get('/hello/')
- post_request = rf.post('/submit/', {'foo': 'bar'})
-
- This class re-uses the :class:`django.test.client.Client` interface. Of which
- you can find the docs here__.
+def get_concrete_model(model_cls):
+ try:
+ return model_cls._meta.concrete_model
+ except AttributeError:
+ # 1.3 does not include concrete model
+ return model_cls
- __ http://www.djangoproject.com/documentation/testing/#the-test-client
- Once you have a `request` object you can pass it to any :func:`view` function,
- just as if that :func:`view` had been hooked up using a URLconf.
- """
- def request(self, **request):
- """
- Similar to parent class, but returns the :obj:`request` object as soon as it
- has created it.
- """
- environ = {
- 'HTTP_COOKIE': self.cookies,
- 'PATH_INFO': '/',
- 'QUERY_STRING': '',
- 'REQUEST_METHOD': 'GET',
- 'SCRIPT_NAME': '',
- 'SERVER_NAME': 'testserver',
- 'SERVER_PORT': 80,
- 'SERVER_PROTOCOL': 'HTTP/1.1',
- }
- environ.update(self.defaults)
- environ.update(request)
- return WSGIRequest(environ)
-
-# django.views.generic.View (1.3 <= Django < 1.4)
-try:
+# First implementation of Django class-based views did not include head method
+# in base View class - https://code.djangoproject.com/ticket/15668
+if django.VERSION >= (1, 4):
from django.views.generic import View
-
- if django.VERSION < (1, 4):
- from django.utils.decorators import classonlymethod
- from django.utils.functional import update_wrapper
-
- # First implementation of Django class-based views did not include head method
- # in base View class - https://code.djangoproject.com/ticket/15668
- class ViewPlusHead(View):
- @classonlymethod
- def as_view(cls, **initkwargs):
- """
- Main entry point for a request-response process.
- """
- # sanitize keyword arguments
- for key in initkwargs:
- if key in cls.http_method_names:
- raise TypeError(u"You tried to pass in the %s method name as a "
- u"keyword argument to %s(). Don't do that."
- % (key, cls.__name__))
- if not hasattr(cls, key):
- raise TypeError(u"%s() received an invalid keyword %r" % (
- cls.__name__, key))
-
- def view(request, *args, **kwargs):
- self = cls(**initkwargs)
- if hasattr(self, 'get') and not hasattr(self, 'head'):
- self.head = self.get
- return self.dispatch(request, *args, **kwargs)
-
- # take name and docstring from class
- update_wrapper(view, cls, updated=())
-
- # and possible attributes set by decorators
- # like csrf_exempt from dispatch
- update_wrapper(view, cls.dispatch, assigned=())
- return view
- View = ViewPlusHead
-
-except ImportError:
- from django import http
+else:
+ from django.views.generic import View as _View
+ from django.utils.decorators import classonlymethod
from django.utils.functional import update_wrapper
- # from django.utils.log import getLogger
- # from django.utils.decorators import classonlymethod
-
- # logger = getLogger('django.request') - We'll just drop support for logger if running Django <= 1.2
- # Might be nice to fix this up sometime to allow djangorestframework.compat.View to match 1.3's View more closely
-
- class View(object):
- """
- Intentionally simple parent class for all views. Only implements
- dispatch-by-method and simple sanity checking.
- """
-
- http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
-
- def __init__(self, **kwargs):
- """
- Constructor. Called in the URLconf; can contain helpful extra
- keyword arguments, and other things.
- """
- # Go through keyword arguments, and either save their values to our
- # instance, or raise an error.
- for key, value in kwargs.iteritems():
- setattr(self, key, value)
- # @classonlymethod - We'll just us classmethod instead if running Django <= 1.2
- @classmethod
+ class View(_View):
+ @classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
@@ -163,29 +59,6 @@ except ImportError:
update_wrapper(view, cls.dispatch, assigned=())
return view
- def dispatch(self, request, *args, **kwargs):
- # Try to dispatch to the right method; if a method doesn't exist,
- # defer to the error handler. Also defer to the error handler if the
- # request method isn't on the approved list.
- if request.method.lower() in self.http_method_names:
- handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
- else:
- handler = self.http_method_not_allowed
- self.request = request
- self.args = args
- self.kwargs = kwargs
- return handler(request, *args, **kwargs)
-
- def http_method_not_allowed(self, request, *args, **kwargs):
- allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
- #logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
- # extra={
- # 'status_code': 405,
- # 'request': self.request
- # }
- #)
- return http.HttpResponseNotAllowed(allowed_methods)
-
# PUT, DELETE do not require CSRF until 1.4. They should. Make it better.
if django.VERSION >= (1, 4):
from django.middleware.csrf import CsrfViewMiddleware
@@ -395,40 +268,63 @@ else:
return self._accept(request)
-
-# Markdown is optional
+# timezone support is new in Django 1.4
try:
- import markdown
- if markdown.version_info < (2, 0):
- raise ImportError('Markdown < 2.0 is not supported.')
+ from django.utils import timezone
+except ImportError:
+ timezone = None
- class CustomSetextHeaderProcessor(markdown.blockprocessors.BlockProcessor):
- """
- Class for markdown < 2.1
+# dateparse is ALSO new in Django 1.4
+try:
+ from django.utils.dateparse import parse_date, parse_datetime
+except ImportError:
+ import datetime
+ import re
- Override `markdown`'s :class:`SetextHeaderProcessor`, so that ==== headers are <h2> and ---- heade
+ date_re = re.compile(
+ r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$'
+ )
+
+ datetime_re = re.compile(
+ r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
+ r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+ r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
+ r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$'
+ )
+
+ time_re = re.compile(
+ r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+ r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
+ )
+
+ def parse_date(value):
+ match = date_re.match(value)
+ if match:
+ kw = dict((k, int(v)) for k, v in match.groupdict().iteritems())
+ return datetime.date(**kw)
+
+ def parse_time(value):
+ match = time_re.match(value)
+ if match:
+ kw = match.groupdict()
+ if kw['microsecond']:
+ kw['microsecond'] = kw['microsecond'].ljust(6, '0')
+ kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
+ return datetime.time(**kw)
+
+ def parse_datetime(value):
+ """Parse datetime, but w/o the timezone awareness in 1.4"""
+ match = datetime_re.match(value)
+ if match:
+ kw = match.groupdict()
+ if kw['microsecond']:
+ kw['microsecond'] = kw['microsecond'].ljust(6, '0')
+ kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None)
+ return datetime.datetime(**kw)
- We use <h1> for the resource name.
- """
- import re
- # Detect Setext-style header. Must be first 2 lines of block.
- RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE)
-
- def test(self, parent, block):
- return bool(self.RE.match(block))
-
- def run(self, parent, blocks):
- lines = blocks.pop(0).split('\n')
- # Determine level. ``=`` is 1 and ``-`` is 2.
- if lines[1].startswith('='):
- level = 2
- else:
- level = 3
- h = markdown.etree.SubElement(parent, 'h%d' % level)
- h.text = lines[0].strip()
- if len(lines) > 2:
- # Block contains additional lines. Add to master blocks for later.
- blocks.insert(0, '\n'.join(lines[2:]))
+# Markdown is optional
+try:
+ import markdown
def apply_markdown(text):
"""
@@ -438,21 +334,13 @@ try:
extensions = ['headerid(level=2)']
safe_mode = False,
-
- if markdown.version_info < (2, 1):
- output_format = markdown.DEFAULT_OUTPUT_FORMAT
-
- md = markdown.Markdown(extensions=markdown.load_extensions(extensions),
- safe_mode=safe_mode,
- output_format=output_format)
- md.parser.blockprocessors['setextheader'] = CustomSetextHeaderProcessor(md.parser)
- else:
- md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
+ md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode)
return md.convert(text)
except ImportError:
apply_markdown = None
+
# Yaml is optional
try:
import yaml
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py
new file mode 100644
index 00000000..a231f191
--- /dev/null
+++ b/rest_framework/decorators.py
@@ -0,0 +1,88 @@
+from rest_framework.views import APIView
+
+
+def api_view(http_method_names):
+
+ """
+ Decorator that converts a function-based view into an APIView subclass.
+ Takes a list of allowed methods for the view as an argument.
+ """
+
+ def decorator(func):
+
+ WrappedAPIView = type(
+ 'WrappedAPIView',
+ (APIView,),
+ {'__doc__': func.__doc__}
+ )
+
+ # Note, the above allows us to set the docstring.
+ # It is the equivelent of:
+ #
+ # class WrappedAPIView(APIView):
+ # pass
+ # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this
+
+ allowed_methods = set(http_method_names) | set(('options',))
+ WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
+
+ def handler(self, *args, **kwargs):
+ return func(*args, **kwargs)
+
+ for method in http_method_names:
+ setattr(WrappedAPIView, method.lower(), handler)
+
+ WrappedAPIView.__name__ = func.__name__
+
+ WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
+ APIView.renderer_classes)
+
+ WrappedAPIView.parser_classes = getattr(func, 'parser_classes',
+ APIView.parser_classes)
+
+ WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes',
+ APIView.authentication_classes)
+
+ WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes',
+ APIView.throttle_classes)
+
+ WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
+ APIView.permission_classes)
+
+ return WrappedAPIView.as_view()
+ return decorator
+
+
+def renderer_classes(renderer_classes):
+ def decorator(func):
+ func.renderer_classes = renderer_classes
+ return func
+ return decorator
+
+
+def parser_classes(parser_classes):
+ def decorator(func):
+ func.parser_classes = parser_classes
+ return func
+ return decorator
+
+
+def authentication_classes(authentication_classes):
+ def decorator(func):
+ func.authentication_classes = authentication_classes
+ return func
+ return decorator
+
+
+def throttle_classes(throttle_classes):
+ def decorator(func):
+ func.throttle_classes = throttle_classes
+ return func
+ return decorator
+
+
+def permission_classes(permission_classes):
+ def decorator(func):
+ func.permission_classes = permission_classes
+ return func
+ return decorator
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
new file mode 100644
index 00000000..89479deb
--- /dev/null
+++ b/rest_framework/exceptions.py
@@ -0,0 +1,78 @@
+"""
+Handled exceptions raised by REST framework.
+
+In addition Django's built in 403 and 404 exceptions are handled.
+(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
+"""
+from rest_framework import status
+
+
+class APIException(Exception):
+ """
+ Base class for REST framework exceptions.
+ Subclasses should provide `.status_code` and `.detail` properties.
+ """
+ pass
+
+
+class ParseError(APIException):
+ status_code = status.HTTP_400_BAD_REQUEST
+ default_detail = 'Malformed request.'
+
+ def __init__(self, detail=None):
+ self.detail = detail or self.default_detail
+
+
+class PermissionDenied(APIException):
+ status_code = status.HTTP_403_FORBIDDEN
+ default_detail = 'You do not have permission to perform this action.'
+
+ def __init__(self, detail=None):
+ self.detail = detail or self.default_detail
+
+
+class MethodNotAllowed(APIException):
+ status_code = status.HTTP_405_METHOD_NOT_ALLOWED
+ default_detail = "Method '%s' not allowed."
+
+ def __init__(self, method, detail=None):
+ self.detail = (detail or self.default_detail) % method
+
+
+class NotAcceptable(APIException):
+ status_code = status.HTTP_406_NOT_ACCEPTABLE
+ default_detail = "Could not satisfy the request's Accept header"
+
+ def __init__(self, detail=None, available_renderers=None):
+ self.detail = detail or self.default_detail
+ self.available_renderers = available_renderers
+
+
+class UnsupportedMediaType(APIException):
+ status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
+ default_detail = "Unsupported media type '%s' in request."
+
+ def __init__(self, media_type, detail=None):
+ self.detail = (detail or self.default_detail) % media_type
+
+
+class Throttled(APIException):
+ status_code = status.HTTP_429_TOO_MANY_REQUESTS
+ default_detail = "Request was throttled."
+ extra_detail = "Expected available in %d second%s."
+
+ def __init__(self, wait=None, detail=None):
+ import math
+ self.wait = wait and math.ceil(wait) or None
+ if wait is not None:
+ format = detail or self.default_detail + self.extra_detail
+ self.detail = format % (self.wait, self.wait != 1 and 's' or '')
+ else:
+ self.detail = detail or self.default_detail
+
+
+class ConfigurationError(Exception):
+ """
+ Indicates an internal server error.
+ """
+ pass
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
new file mode 100644
index 00000000..1d6d760e
--- /dev/null
+++ b/rest_framework/fields.py
@@ -0,0 +1,695 @@
+import copy
+import datetime
+import inspect
+import warnings
+
+from django.core import validators
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.core.urlresolvers import resolve, get_script_prefix
+from django.conf import settings
+from django.forms import widgets
+from django.utils.encoding import is_protected_type, smart_unicode
+from django.utils.translation import ugettext_lazy as _
+from rest_framework.reverse import reverse
+from rest_framework.compat import parse_date, parse_datetime
+from rest_framework.compat import timezone
+from urlparse import urlparse
+
+
+def is_simple_callable(obj):
+ """
+ True if the object is a callable that takes no arguments.
+ """
+ return (
+ (inspect.isfunction(obj) and not inspect.getargspec(obj)[0]) or
+ (inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1)
+ )
+
+
+class Field(object):
+ creation_counter = 0
+ empty = ''
+ type_name = None
+
+ def __init__(self, source=None):
+ self.parent = None
+
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
+
+ self.source = source
+
+ def initialize(self, parent):
+ """
+ Called to set up a field prior to field_to_native or field_from_native.
+
+ parent - The parent serializer.
+ model_field - The model field this field corresponds to, if one exists.
+ """
+ self.parent = parent
+ self.root = parent.root or parent
+ self.context = self.root.context
+
+ def field_from_native(self, data, field_name, into):
+ """
+ Given a dictionary and a field name, updates the dictionary `into`,
+ with the field and it's deserialized value.
+ """
+ return
+
+ def field_to_native(self, obj, field_name):
+ """
+ Given and object and a field name, returns the value that should be
+ serialized for that field.
+ """
+ if obj is None:
+ return self.empty
+
+ if self.source == '*':
+ return self.to_native(obj)
+
+ if self.source:
+ value = obj
+ for component in self.source.split('.'):
+ value = getattr(value, component)
+ if is_simple_callable(value):
+ value = value()
+ else:
+ value = getattr(obj, field_name)
+ return self.to_native(value)
+
+ def to_native(self, value):
+ """
+ Converts the field's value into it's simple representation.
+ """
+ if is_simple_callable(value):
+ value = value()
+
+ if is_protected_type(value):
+ return value
+ elif hasattr(value, '__iter__') and not isinstance(value, (dict, basestring)):
+ return [self.to_native(item) for item in value]
+ return smart_unicode(value)
+
+ def attributes(self):
+ """
+ Returns a dictionary of attributes to be used when serializing to xml.
+ """
+ if self.type_name:
+ return {'type': self.type_name}
+ return {}
+
+
+class WritableField(Field):
+ """
+ Base for read/write fields.
+ """
+ default_validators = []
+ default_error_messages = {
+ 'required': _('This field is required.'),
+ 'invalid': _('Invalid value.'),
+ }
+ widget = widgets.TextInput
+ default = None
+
+ def __init__(self, source=None, read_only=False, required=None,
+ validators=[], error_messages=None, widget=None,
+ default=None, blank=None):
+
+ super(WritableField, self).__init__(source=source)
+
+ self.read_only = read_only
+ if required is None:
+ self.required = not(read_only)
+ else:
+ assert not read_only, "Cannot set required=True and read_only=True"
+ self.required = required
+
+ messages = {}
+ for c in reversed(self.__class__.__mro__):
+ messages.update(getattr(c, 'default_error_messages', {}))
+ messages.update(error_messages or {})
+ self.error_messages = messages
+
+ self.validators = self.default_validators + validators
+ self.default = default or self.default
+ self.blank = blank
+
+ # Widgets are ony used for HTML forms.
+ widget = widget or self.widget
+ if isinstance(widget, type):
+ widget = widget()
+ self.widget = widget
+
+ def validate(self, value):
+ if value in validators.EMPTY_VALUES and self.required:
+ raise ValidationError(self.error_messages['required'])
+
+ def run_validators(self, value):
+ if value in validators.EMPTY_VALUES:
+ return
+ errors = []
+ for v in self.validators:
+ try:
+ v(value)
+ except ValidationError as e:
+ if hasattr(e, 'code') and e.code in self.error_messages:
+ message = self.error_messages[e.code]
+ if e.params:
+ message = message % e.params
+ errors.append(message)
+ else:
+ errors.extend(e.messages)
+ if errors:
+ raise ValidationError(errors)
+
+ def field_from_native(self, data, field_name, into):
+ """
+ Given a dictionary and a field name, updates the dictionary `into`,
+ with the field and it's deserialized value.
+ """
+ if self.read_only:
+ return
+
+ try:
+ native = data[field_name]
+ except KeyError:
+ if self.default is not None:
+ native = self.default
+ else:
+ if self.required:
+ raise ValidationError(self.error_messages['required'])
+ return
+
+ value = self.from_native(native)
+ if self.source == '*':
+ if value:
+ into.update(value)
+ else:
+ self.validate(value)
+ self.run_validators(value)
+ into[self.source or field_name] = value
+
+ def from_native(self, value):
+ """
+ Reverts a simple representation back to the field's value.
+ """
+ return value
+
+
+class ModelField(WritableField):
+ """
+ A generic field that can be used against an arbitrary model field.
+ """
+ def __init__(self, *args, **kwargs):
+ try:
+ self.model_field = kwargs.pop('model_field')
+ except:
+ raise ValueError("ModelField requires 'model_field' kwarg")
+ super(ModelField, self).__init__(*args, **kwargs)
+
+ def from_native(self, value):
+ try:
+ rel = self.model_field.rel
+ except:
+ return self.model_field.to_python(value)
+ return rel.to._meta.get_field(rel.field_name).to_python(value)
+
+ def field_to_native(self, obj, field_name):
+ value = self.model_field._get_val_from_obj(obj)
+ if is_protected_type(value):
+ return value
+ return self.model_field.value_to_string(obj)
+
+ def attributes(self):
+ return {
+ "type": self.model_field.get_internal_type()
+ }
+
+##### Relational fields #####
+
+
+class RelatedField(WritableField):
+ """
+ Base class for related model fields.
+ """
+ def __init__(self, *args, **kwargs):
+ self.queryset = kwargs.pop('queryset', None)
+ super(RelatedField, self).__init__(*args, **kwargs)
+
+ def field_to_native(self, obj, field_name):
+ value = getattr(obj, self.source or field_name)
+ return self.to_native(value)
+
+ def field_from_native(self, data, field_name, into):
+ if self.read_only:
+ return
+
+ value = data.get(field_name)
+ into[(self.source or field_name)] = self.from_native(value)
+
+
+class ManyRelatedMixin(object):
+ """
+ Mixin to convert a related field to a many related field.
+ """
+ def field_to_native(self, obj, field_name):
+ value = getattr(obj, self.source or field_name)
+ return [self.to_native(item) for item in value.all()]
+
+ def field_from_native(self, data, field_name, into):
+ if self.read_only:
+ return
+
+ try:
+ # Form data
+ value = data.getlist(self.source or field_name)
+ except:
+ # Non-form data
+ value = data.get(self.source or field_name)
+ else:
+ if value == ['']:
+ value = []
+ into[field_name] = [self.from_native(item) for item in value]
+
+
+class ManyRelatedField(ManyRelatedMixin, RelatedField):
+ """
+ Base class for related model managers.
+ """
+ pass
+
+
+### PrimaryKey relationships
+
+class PrimaryKeyRelatedField(RelatedField):
+ """
+ Serializes a related field or related object to a pk value.
+ """
+
+ def to_native(self, pk):
+ return pk
+
+ def from_native(self, data):
+ if self.queryset is None:
+ raise Exception('Writable related fields must include a `queryset` argument')
+
+ try:
+ return self.queryset.get(pk=data)
+ except ObjectDoesNotExist:
+ raise ValidationError('Invalid hyperlink - object does not exist.')
+
+ def field_to_native(self, obj, field_name):
+ try:
+ # Prefer obj.serializable_value for performance reasons
+ pk = obj.serializable_value(self.source or field_name)
+ except AttributeError:
+ # RelatedObject (reverse relationship)
+ obj = getattr(obj, self.source or field_name)
+ return self.to_native(obj.pk)
+ # Forward relationship
+ return self.to_native(pk)
+
+
+class ManyPrimaryKeyRelatedField(ManyRelatedField):
+ """
+ Serializes a to-many related field or related manager to a pk value.
+ """
+ def to_native(self, pk):
+ return pk
+
+ def field_to_native(self, obj, field_name):
+ try:
+ # Prefer obj.serializable_value for performance reasons
+ queryset = obj.serializable_value(self.source or field_name)
+ except AttributeError:
+ # RelatedManager (reverse relationship)
+ queryset = getattr(obj, self.source or field_name)
+ return [self.to_native(item.pk) for item in queryset.all()]
+ # Forward relationship
+ return [self.to_native(item.pk) for item in queryset.all()]
+
+
+### Hyperlinked relationships
+
+class HyperlinkedRelatedField(RelatedField):
+ pk_url_kwarg = 'pk'
+ slug_url_kwarg = 'slug'
+ slug_field = 'slug'
+
+ def __init__(self, *args, **kwargs):
+ try:
+ self.view_name = kwargs.pop('view_name')
+ except:
+ raise ValueError("Hyperlinked field requires 'view_name' kwarg")
+ self.format = kwargs.pop('format', None)
+ super(HyperlinkedRelatedField, self).__init__(*args, **kwargs)
+
+ def to_native(self, obj):
+ view_name = self.view_name
+ request = self.context.get('request', None)
+ format = self.format or self.context.get('format', None)
+ kwargs = {self.pk_url_kwarg: obj.pk}
+ try:
+ return reverse(view_name, kwargs=kwargs, request=request, format=format)
+ except:
+ pass
+
+ slug = getattr(obj, self.slug_field, None)
+
+ if not slug:
+ raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
+
+ kwargs = {self.slug_url_kwarg: slug}
+ try:
+ return reverse(self.view_name, kwargs=kwargs, request=request, format=format)
+ except:
+ pass
+
+ kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
+ try:
+ return reverse(self.view_name, kwargs=kwargs, request=request, format=format)
+ except:
+ pass
+
+ raise ValidationError('Could not resolve URL for field using view name "%s"', view_name)
+
+ def from_native(self, value):
+ # Convert URL -> model instance pk
+ # TODO: Use values_list
+ if self.queryset is None:
+ raise Exception('Writable related fields must include a `queryset` argument')
+
+ if value.startswith('http:') or value.startswith('https:'):
+ # If needed convert absolute URLs to relative path
+ value = urlparse(value).path
+ prefix = get_script_prefix()
+ if value.startswith(prefix):
+ value = '/' + value[len(prefix):]
+
+ try:
+ match = resolve(value)
+ except:
+ raise ValidationError('Invalid hyperlink - No URL match')
+
+ if match.url_name != self.view_name:
+ raise ValidationError('Invalid hyperlink - Incorrect URL match')
+
+ pk = match.kwargs.get(self.pk_url_kwarg, None)
+ slug = match.kwargs.get(self.slug_url_kwarg, None)
+
+ # Try explicit primary key.
+ if pk is not None:
+ queryset = self.queryset.filter(pk=pk)
+ # Next, try looking up by slug.
+ elif slug is not None:
+ slug_field = self.get_slug_field()
+ queryset = self.queryset.filter(**{slug_field: slug})
+ # If none of those are defined, it's an error.
+ else:
+ raise ValidationError('Invalid hyperlink')
+
+ try:
+ obj = queryset.get()
+ except ObjectDoesNotExist:
+ raise ValidationError('Invalid hyperlink - object does not exist.')
+ return obj
+
+
+class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField):
+ pass
+
+
+class HyperlinkedIdentityField(Field):
+ """
+ A field that represents the model's identity using a hyperlink.
+ """
+ def __init__(self, *args, **kwargs):
+ # TODO: Make this mandatory, and have the HyperlinkedModelSerializer
+ # set it on-the-fly
+ self.view_name = kwargs.pop('view_name', None)
+ self.format = kwargs.pop('format', None)
+ super(HyperlinkedIdentityField, self).__init__(*args, **kwargs)
+
+ def field_to_native(self, obj, field_name):
+ request = self.context.get('request', None)
+ format = self.format or self.context.get('format', None)
+ view_name = self.view_name or self.parent.opts.view_name
+ view_kwargs = {'pk': obj.pk}
+ return reverse(view_name, kwargs=view_kwargs, request=request, format=format)
+
+
+##### Typed Fields #####
+
+class BooleanField(WritableField):
+ type_name = 'BooleanField'
+ widget = widgets.CheckboxInput
+ default_error_messages = {
+ 'invalid': _(u"'%s' value must be either True or False."),
+ }
+ empty = False
+
+ # Note: we set default to `False` in order to fill in missing value not
+ # supplied by html form. TODO: Fix so that only html form input gets
+ # this behavior.
+ default = False
+
+ def from_native(self, value):
+ if value in ('t', 'True', '1'):
+ return True
+ if value in ('f', 'False', '0'):
+ return False
+ return bool(value)
+
+
+class CharField(WritableField):
+ type_name = 'CharField'
+
+ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
+ self.max_length, self.min_length = max_length, min_length
+ super(CharField, self).__init__(*args, **kwargs)
+ if min_length is not None:
+ self.validators.append(validators.MinLengthValidator(min_length))
+ if max_length is not None:
+ self.validators.append(validators.MaxLengthValidator(max_length))
+
+ def validate(self, value):
+ """
+ Validates that the value is supplied (if required).
+ """
+ # if empty string and allow blank
+ if self.blank and not value:
+ return
+ else:
+ super(CharField, self).validate(value)
+
+ def from_native(self, value):
+ if isinstance(value, basestring) or value is None:
+ return value
+ return smart_unicode(value)
+
+
+class ChoiceField(WritableField):
+ type_name = 'ChoiceField'
+ widget = widgets.Select
+ default_error_messages = {
+ 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
+ }
+
+ def __init__(self, choices=(), *args, **kwargs):
+ super(ChoiceField, self).__init__(*args, **kwargs)
+ self.choices = choices
+
+ def _get_choices(self):
+ return self._choices
+
+ def _set_choices(self, value):
+ # Setting choices also sets the choices on the widget.
+ # choices can be any iterable, but we call list() on it because
+ # it will be consumed more than once.
+ self._choices = self.widget.choices = list(value)
+
+ choices = property(_get_choices, _set_choices)
+
+ def validate(self, value):
+ """
+ Validates that the input is in self.choices.
+ """
+ super(ChoiceField, self).validate(value)
+ if value and not self.valid_value(value):
+ raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
+
+ def valid_value(self, value):
+ """
+ Check to see if the provided value is a valid choice.
+ """
+ for k, v in self.choices:
+ if isinstance(v, (list, tuple)):
+ # This is an optgroup, so look inside the group for options
+ for k2, v2 in v:
+ if value == smart_unicode(k2):
+ return True
+ else:
+ if value == smart_unicode(k):
+ return True
+ return False
+
+
+class EmailField(CharField):
+ type_name = 'EmailField'
+
+ default_error_messages = {
+ 'invalid': _('Enter a valid e-mail address.'),
+ }
+ default_validators = [validators.validate_email]
+
+ def from_native(self, value):
+ ret = super(EmailField, self).from_native(value)
+ if ret is None:
+ return None
+ return ret.strip()
+
+ def __deepcopy__(self, memo):
+ result = copy.copy(self)
+ memo[id(self)] = result
+ #result.widget = copy.deepcopy(self.widget, memo)
+ result.validators = self.validators[:]
+ return result
+
+
+class DateField(WritableField):
+ type_name = 'DateField'
+
+ default_error_messages = {
+ 'invalid': _(u"'%s' value has an invalid date format. It must be "
+ u"in YYYY-MM-DD format."),
+ 'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) "
+ u"but it is an invalid date."),
+ }
+ empty = None
+
+ def from_native(self, value):
+ if value in validators.EMPTY_VALUES:
+ return None
+
+ if isinstance(value, datetime.datetime):
+ if timezone and settings.USE_TZ and timezone.is_aware(value):
+ # Convert aware datetimes to the default time zone
+ # before casting them to dates (#17742).
+ default_timezone = timezone.get_default_timezone()
+ value = timezone.make_naive(value, default_timezone)
+ return value.date()
+ if isinstance(value, datetime.date):
+ return value
+
+ try:
+ parsed = parse_date(value)
+ if parsed is not None:
+ return parsed
+ except ValueError:
+ msg = self.error_messages['invalid_date'] % value
+ raise ValidationError(msg)
+
+ msg = self.error_messages['invalid'] % value
+ raise ValidationError(msg)
+
+
+class DateTimeField(WritableField):
+ type_name = 'DateTimeField'
+
+ default_error_messages = {
+ 'invalid': _(u"'%s' value has an invalid format. It must be in "
+ u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
+ 'invalid_date': _(u"'%s' value has the correct format "
+ u"(YYYY-MM-DD) but it is an invalid date."),
+ 'invalid_datetime': _(u"'%s' value has the correct format "
+ u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
+ u"but it is an invalid date/time."),
+ }
+ empty = None
+
+ def from_native(self, value):
+ if value in validators.EMPTY_VALUES:
+ return None
+
+ if isinstance(value, datetime.datetime):
+ return value
+ if isinstance(value, datetime.date):
+ value = datetime.datetime(value.year, value.month, value.day)
+ if settings.USE_TZ:
+ # For backwards compatibility, interpret naive datetimes in
+ # local time. This won't work during DST change, but we can't
+ # do much about it, so we let the exceptions percolate up the
+ # call stack.
+ warnings.warn(u"DateTimeField received a naive datetime (%s)"
+ u" while time zone support is active." % value,
+ RuntimeWarning)
+ default_timezone = timezone.get_default_timezone()
+ value = timezone.make_aware(value, default_timezone)
+ return value
+
+ try:
+ parsed = parse_datetime(value)
+ if parsed is not None:
+ return parsed
+ except ValueError:
+ msg = self.error_messages['invalid_datetime'] % value
+ raise ValidationError(msg)
+
+ try:
+ parsed = parse_date(value)
+ if parsed is not None:
+ return datetime.datetime(parsed.year, parsed.month, parsed.day)
+ except ValueError:
+ msg = self.error_messages['invalid_date'] % value
+ raise ValidationError(msg)
+
+ msg = self.error_messages['invalid'] % value
+ raise ValidationError(msg)
+
+
+class IntegerField(WritableField):
+ type_name = 'IntegerField'
+
+ default_error_messages = {
+ 'invalid': _('Enter a whole number.'),
+ 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'),
+ 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'),
+ }
+
+ def __init__(self, max_value=None, min_value=None, *args, **kwargs):
+ self.max_value, self.min_value = max_value, min_value
+ super(IntegerField, self).__init__(*args, **kwargs)
+
+ if max_value is not None:
+ self.validators.append(validators.MaxValueValidator(max_value))
+ if min_value is not None:
+ self.validators.append(validators.MinValueValidator(min_value))
+
+ def from_native(self, value):
+ if value in validators.EMPTY_VALUES:
+ return None
+
+ try:
+ value = int(str(value))
+ except (ValueError, TypeError):
+ raise ValidationError(self.error_messages['invalid'])
+ return value
+
+
+class FloatField(WritableField):
+ type_name = 'FloatField'
+
+ default_error_messages = {
+ 'invalid': _("'%s' value must be a float."),
+ }
+
+ def from_native(self, value):
+ if value in validators.EMPTY_VALUES:
+ return None
+
+ try:
+ return float(value)
+ except (TypeError, ValueError):
+ msg = self.error_messages['invalid'] % value
+ raise ValidationError(msg)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
new file mode 100644
index 00000000..27540a57
--- /dev/null
+++ b/rest_framework/generics.py
@@ -0,0 +1,187 @@
+"""
+Generic views that provide commonly needed behaviour.
+"""
+
+from rest_framework import views, mixins
+from rest_framework.settings import api_settings
+from django.views.generic.detail import SingleObjectMixin
+from django.views.generic.list import MultipleObjectMixin
+
+
+### Base classes for the generic views ###
+
+class GenericAPIView(views.APIView):
+ """
+ Base class for all other generic views.
+ """
+ serializer_class = None
+ model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
+
+ def get_serializer_context(self):
+ """
+ Extra context provided to the serializer class.
+ """
+ return {
+ 'request': self.request,
+ 'format': self.format_kwarg,
+ 'view': self
+ }
+
+ def get_serializer_class(self):
+ """
+ Return the class to use for the serializer.
+ Use `self.serializer_class`, falling back to constructing a
+ model serializer class from `self.model_serializer_class`
+ """
+ serializer_class = self.serializer_class
+
+ if serializer_class is None:
+ class DefaultSerializer(self.model_serializer_class):
+ class Meta:
+ model = self.model
+ serializer_class = DefaultSerializer
+
+ return serializer_class
+
+ def get_serializer(self, data=None, files=None, instance=None):
+ # TODO: add support for files
+ # TODO: add support for seperate serializer/deserializer
+ serializer_class = self.get_serializer_class()
+ context = self.get_serializer_context()
+ return serializer_class(data, instance=instance, context=context)
+
+
+class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
+ """
+ Base class for generic views onto a queryset.
+ """
+
+ pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
+ paginate_by = api_settings.PAGINATE_BY
+
+ def get_pagination_serializer_class(self):
+ """
+ Return the class to use for the pagination serializer.
+ """
+ class SerializerClass(self.pagination_serializer_class):
+ class Meta:
+ object_serializer_class = self.get_serializer_class()
+
+ return SerializerClass
+
+ def get_pagination_serializer(self, page=None):
+ pagination_serializer_class = self.get_pagination_serializer_class()
+ context = self.get_serializer_context()
+ return pagination_serializer_class(instance=page, context=context)
+
+
+class SingleObjectAPIView(SingleObjectMixin, GenericAPIView):
+ """
+ Base class for generic views onto a model instance.
+ """
+ pk_url_kwarg = 'pk' # Not provided in Django 1.3
+ slug_url_kwarg = 'slug' # Not provided in Django 1.3
+
+ def get_object(self):
+ """
+ Override default to add support for object-level permissions.
+ """
+ obj = super(SingleObjectAPIView, self).get_object()
+ if not self.has_permission(self.request, obj):
+ self.permission_denied(self.request)
+ return obj
+
+
+### Concrete view classes that provide method handlers ###
+### by composing the mixin classes with a base view. ###
+
+
+class CreateAPIView(mixins.CreateModelMixin,
+ GenericAPIView):
+
+ """
+ Concrete view for creating a model instance.
+ """
+ def post(self, request, *args, **kwargs):
+ return self.create(request, *args, **kwargs)
+
+
+class ListAPIView(mixins.ListModelMixin,
+ MultipleObjectAPIView):
+ """
+ Concrete view for listing a queryset.
+ """
+ def get(self, request, *args, **kwargs):
+ return self.list(request, *args, **kwargs)
+
+
+class RetrieveAPIView(mixins.RetrieveModelMixin,
+ SingleObjectAPIView):
+ """
+ Concrete view for retrieving a model instance.
+ """
+ def get(self, request, *args, **kwargs):
+ return self.retrieve(request, *args, **kwargs)
+
+
+class DestroyAPIView(mixins.DestroyModelMixin,
+ SingleObjectAPIView):
+
+ """
+ Concrete view for deleting a model instance.
+ """
+ def delete(self, request, *args, **kwargs):
+ return self.destroy(request, *args, **kwargs)
+
+
+class UpdateAPIView(mixins.UpdateModelMixin,
+ SingleObjectAPIView):
+
+ """
+ Concrete view for updating a model instance.
+ """
+ def put(self, request, *args, **kwargs):
+ return self.update(request, *args, **kwargs)
+
+
+class ListCreateAPIView(mixins.ListModelMixin,
+ mixins.CreateModelMixin,
+ MultipleObjectAPIView):
+ """
+ Concrete view for listing a queryset or creating a model instance.
+ """
+ def get(self, request, *args, **kwargs):
+ return self.list(request, *args, **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ return self.create(request, *args, **kwargs)
+
+
+class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
+ mixins.DestroyModelMixin,
+ SingleObjectAPIView):
+ """
+ Concrete view for retrieving or deleting a model instance.
+ """
+ def get(self, request, *args, **kwargs):
+ return self.retrieve(request, *args, **kwargs)
+
+ def delete(self, request, *args, **kwargs):
+ return self.destroy(request, *args, **kwargs)
+
+
+class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
+ mixins.UpdateModelMixin,
+ mixins.DestroyModelMixin,
+ SingleObjectAPIView):
+ """
+ Concrete view for retrieving, updating or deleting a model instance.
+ """
+ def get(self, request, *args, **kwargs):
+ return self.retrieve(request, *args, **kwargs)
+
+ def put(self, request, *args, **kwargs):
+ return self.update(request, *args, **kwargs)
+
+ def delete(self, request, *args, **kwargs):
+ return self.destroy(request, *args, **kwargs)
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
new file mode 100644
index 00000000..8873e4ae
--- /dev/null
+++ b/rest_framework/mixins.py
@@ -0,0 +1,116 @@
+"""
+Basic building blocks for generic class based views.
+
+We don't bind behaviour to http method handlers yet,
+which allows mixin classes to be composed in interesting ways.
+
+Eg. Use mixins to build a Resource class, and have a Router class
+ perform the binding of http methods to actions for us.
+"""
+from django.http import Http404
+from rest_framework import status
+from rest_framework.response import Response
+
+
+class CreateModelMixin(object):
+ """
+ Create a model instance.
+ Should be mixed in with any `BaseView`.
+ """
+ def create(self, request, *args, **kwargs):
+ serializer = self.get_serializer(data=request.DATA)
+ if serializer.is_valid():
+ self.pre_save(serializer.object)
+ self.object = serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def pre_save(self, obj):
+ pass
+
+
+class ListModelMixin(object):
+ """
+ List a queryset.
+ Should be mixed in with `MultipleObjectBaseView`.
+ """
+ empty_error = u"Empty list and '%(class_name)s.allow_empty' is False."
+
+ def list(self, request, *args, **kwargs):
+ self.object_list = self.get_queryset()
+
+ # Default is to allow empty querysets. This can be altered by setting
+ # `.allow_empty = False`, to raise 404 errors on empty querysets.
+ allow_empty = self.get_allow_empty()
+ if not allow_empty and len(self.object_list) == 0:
+ error_args = {'class_name': self.__class__.__name__}
+ raise Http404(self.empty_error % error_args)
+
+ # Pagination size is set by the `.paginate_by` attribute,
+ # which may be `None` to disable pagination.
+ page_size = self.get_paginate_by(self.object_list)
+ if page_size:
+ packed = self.paginate_queryset(self.object_list, page_size)
+ paginator, page, queryset, is_paginated = packed
+ serializer = self.get_pagination_serializer(page)
+ else:
+ serializer = self.get_serializer(instance=self.object_list)
+
+ return Response(serializer.data)
+
+
+class RetrieveModelMixin(object):
+ """
+ Retrieve a model instance.
+ Should be mixed in with `SingleObjectBaseView`.
+ """
+ def retrieve(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ serializer = self.get_serializer(instance=self.object)
+ return Response(serializer.data)
+
+
+class UpdateModelMixin(object):
+ """
+ Update a model instance.
+ Should be mixed in with `SingleObjectBaseView`.
+ """
+ def update(self, request, *args, **kwargs):
+ try:
+ self.object = self.get_object()
+ except Http404:
+ self.object = None
+
+ serializer = self.get_serializer(data=request.DATA, instance=self.object)
+
+ if serializer.is_valid():
+ self.pre_save(serializer.object)
+ self.object = serializer.save()
+ return Response(serializer.data)
+
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def pre_save(self, obj):
+ """
+ Set any attributes on the object that are implicit in the request.
+ """
+ # pk and/or slug attributes are implicit in the URL.
+ pk = self.kwargs.get(self.pk_url_kwarg, None)
+ if pk:
+ setattr(obj, 'pk', pk)
+
+ slug = self.kwargs.get(self.slug_url_kwarg, None)
+ if slug:
+ slug_field = self.get_slug_field()
+ setattr(obj, slug_field, slug)
+
+
+class DestroyModelMixin(object):
+ """
+ Destroy a model instance.
+ Should be mixed in with `SingleObjectBaseView`.
+ """
+ def destroy(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ self.object.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/djangorestframework/models.py b/rest_framework/models.py
index 5b53a526..5b53a526 100644
--- a/djangorestframework/models.py
+++ b/rest_framework/models.py
diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py
new file mode 100644
index 00000000..dae38477
--- /dev/null
+++ b/rest_framework/negotiation.py
@@ -0,0 +1,82 @@
+from django.http import Http404
+from rest_framework import exceptions
+from rest_framework.settings import api_settings
+from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
+
+
+class BaseContentNegotiation(object):
+ def select_parser(self, request, parsers):
+ raise NotImplementedError('.select_parser() must be implemented')
+
+ def select_renderer(self, request, renderers, format_suffix=None):
+ raise NotImplementedError('.select_renderer() must be implemented')
+
+
+class DefaultContentNegotiation(BaseContentNegotiation):
+ settings = api_settings
+
+ def select_parser(self, request, parsers):
+ """
+ Given a list of parsers and a media type, return the appropriate
+ parser to handle the incoming request.
+ """
+ for parser in parsers:
+ if media_type_matches(parser.media_type, request.content_type):
+ return parser
+ return None
+
+ def select_renderer(self, request, renderers, format_suffix=None):
+ """
+ Given a request and a list of renderers, return a two-tuple of:
+ (renderer, media type).
+ """
+ # Allow URL style format override. eg. "?format=json
+ format_query_param = self.settings.URL_FORMAT_OVERRIDE
+ format = format_suffix or request.GET.get(format_query_param)
+
+ if format:
+ renderers = self.filter_renderers(renderers, format)
+
+ accepts = self.get_accept_list(request)
+
+ # Check the acceptable media types against each renderer,
+ # attempting more specific media types first
+ # NB. The inner loop here isn't as bad as it first looks :)
+ # Worst case is we're looping over len(accept_list) * len(self.renderers)
+ for media_type_set in order_by_precedence(accepts):
+ for renderer in renderers:
+ for media_type in media_type_set:
+ if media_type_matches(renderer.media_type, media_type):
+ # Return the most specific media type as accepted.
+ if len(renderer.media_type) > len(media_type):
+ # Eg client requests '*/*'
+ # Accepted media type is 'application/json'
+ return renderer, renderer.media_type
+ else:
+ # Eg client requests 'application/json; indent=8'
+ # Accepted media type is 'application/json; indent=8'
+ return renderer, media_type
+
+ raise exceptions.NotAcceptable(available_renderers=renderers)
+
+ def filter_renderers(self, renderers, format):
+ """
+ If there is a '.json' style format suffix, filter the renderers
+ so that we only negotiation against those that accept that format.
+ """
+ renderers = [renderer for renderer in renderers
+ if renderer.format == format]
+ if not renderers:
+ raise Http404
+ return renderers
+
+ def get_accept_list(self, request):
+ """
+ Given the incoming request, return a tokenised list of media
+ type strings.
+
+ Allows URL style accept override. eg. "?accept=application/json"
+ """
+ header = request.META.get('HTTP_ACCEPT', '*/*')
+ header = request.GET.get(self.settings.URL_ACCEPT_OVERRIDE, header)
+ return [token.strip() for token in header.split(',')]
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
new file mode 100644
index 00000000..131718fd
--- /dev/null
+++ b/rest_framework/pagination.py
@@ -0,0 +1,80 @@
+from rest_framework import serializers
+
+# TODO: Support URLconf kwarg-style paging
+
+
+class NextPageField(serializers.Field):
+ """
+ Field that returns a link to the next page in paginated results.
+ """
+ def to_native(self, value):
+ if not value.has_next():
+ return None
+ page = value.next_page_number()
+ request = self.context.get('request')
+ relative_url = '?page=%d' % page
+ if request:
+ return request.build_absolute_uri(relative_url)
+ return relative_url
+
+
+class PreviousPageField(serializers.Field):
+ """
+ Field that returns a link to the previous page in paginated results.
+ """
+ def to_native(self, value):
+ if not value.has_previous():
+ return None
+ page = value.previous_page_number()
+ request = self.context.get('request')
+ relative_url = '?page=%d' % page
+ if request:
+ return request.build_absolute_uri('?page=%d' % page)
+ return relative_url
+
+
+class PaginationSerializerOptions(serializers.SerializerOptions):
+ """
+ An object that stores the options that may be provided to a
+ pagination serializer by using the inner `Meta` class.
+
+ Accessible on the instance as `serializer.opts`.
+ """
+ def __init__(self, meta):
+ super(PaginationSerializerOptions, self).__init__(meta)
+ self.object_serializer_class = getattr(meta, 'object_serializer_class',
+ serializers.Field)
+
+
+class BasePaginationSerializer(serializers.Serializer):
+ """
+ A base class for pagination serializers to inherit from,
+ to make implementing custom serializers more easy.
+ """
+ _options_class = PaginationSerializerOptions
+ results_field = 'results'
+
+ def __init__(self, *args, **kwargs):
+ """
+ Override init to add in the object serializer field on-the-fly.
+ """
+ super(BasePaginationSerializer, self).__init__(*args, **kwargs)
+ results_field = self.results_field
+ object_serializer = self.opts.object_serializer_class
+ self.fields[results_field] = object_serializer(source='object_list')
+
+ def to_native(self, obj):
+ """
+ Prevent default behaviour of iterating over elements, and serializing
+ each in turn.
+ """
+ return self.convert_object(obj)
+
+
+class PaginationSerializer(BasePaginationSerializer):
+ """
+ A default implementation of a pagination serializer.
+ """
+ count = serializers.Field(source='paginator.count')
+ next = NextPageField(source='*')
+ previous = PreviousPageField(source='*')
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
new file mode 100644
index 00000000..4841676c
--- /dev/null
+++ b/rest_framework/parsers.py
@@ -0,0 +1,189 @@
+"""
+Parsers are used to parse the content of incoming HTTP requests.
+
+They give us a generic way of being able to handle various media types
+on the request, such as form content or json encoded data.
+"""
+
+from django.http import QueryDict
+from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser
+from django.http.multipartparser import MultiPartParserError
+from django.utils import simplejson as json
+from rest_framework.compat import yaml, ETParseError
+from rest_framework.exceptions import ParseError
+from xml.etree import ElementTree as ET
+from xml.parsers.expat import ExpatError
+import datetime
+import decimal
+
+
+class DataAndFiles(object):
+ def __init__(self, data, files):
+ self.data = data
+ self.files = files
+
+
+class BaseParser(object):
+ """
+ All parsers should extend `BaseParser`, specifying a `media_type`
+ attribute, and overriding the `.parse()` method.
+ """
+
+ media_type = None
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Given a stream to read from, return the parsed representation.
+ Should return parsed data, or a `DataAndFiles` object consisting of the
+ parsed data and files.
+ """
+ raise NotImplementedError(".parse() must be overridden.")
+
+
+class JSONParser(BaseParser):
+ """
+ Parses JSON-serialized data.
+ """
+
+ media_type = 'application/json'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a 2-tuple of `(data, files)`.
+
+ `data` will be an object which is the parsed content of the response.
+ `files` will always be `None`.
+ """
+ try:
+ return json.load(stream)
+ except ValueError, exc:
+ raise ParseError('JSON parse error - %s' % unicode(exc))
+
+
+class YAMLParser(BaseParser):
+ """
+ Parses YAML-serialized data.
+ """
+
+ media_type = 'application/yaml'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a 2-tuple of `(data, files)`.
+
+ `data` will be an object which is the parsed content of the response.
+ `files` will always be `None`.
+ """
+ try:
+ return yaml.safe_load(stream)
+ except (ValueError, yaml.parser.ParserError), exc:
+ raise ParseError('YAML parse error - %s' % unicode(exc))
+
+
+class FormParser(BaseParser):
+ """
+ Parser for form data.
+ """
+
+ media_type = 'application/x-www-form-urlencoded'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a 2-tuple of `(data, files)`.
+
+ `data` will be a :class:`QueryDict` containing all the form parameters.
+ `files` will always be :const:`None`.
+ """
+ data = QueryDict(stream.read())
+ return data
+
+
+class MultiPartParser(BaseParser):
+ """
+ Parser for multipart form data, which may include file data.
+ """
+
+ media_type = 'multipart/form-data'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a DataAndFiles object.
+
+ `.data` will be a `QueryDict` containing all the form parameters.
+ `.files` will be a `QueryDict` containing all the form files.
+ """
+ parser_context = parser_context or {}
+ request = parser_context['request']
+ meta = request.META
+ upload_handlers = request.upload_handlers
+
+ try:
+ parser = DjangoMultiPartParser(meta, stream, upload_handlers)
+ data, files = parser.parse()
+ return DataAndFiles(data, files)
+ except MultiPartParserError, exc:
+ raise ParseError('Multipart form parse error - %s' % unicode(exc))
+
+
+class XMLParser(BaseParser):
+ """
+ XML parser.
+ """
+
+ media_type = 'application/xml'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ try:
+ tree = ET.parse(stream)
+ except (ExpatError, ETParseError, ValueError), exc:
+ raise ParseError('XML parse error - %s' % unicode(exc))
+ data = self._xml_convert(tree.getroot())
+
+ return data
+
+ def _xml_convert(self, element):
+ """
+ convert the xml `element` into the corresponding python object
+ """
+
+ children = element.getchildren()
+
+ if len(children) == 0:
+ return self._type_convert(element.text)
+ else:
+ # if the fist child tag is list-item means all children are list-item
+ if children[0].tag == "list-item":
+ data = []
+ for child in children:
+ data.append(self._xml_convert(child))
+ else:
+ data = {}
+ for child in children:
+ data[child.tag] = self._xml_convert(child)
+
+ return data
+
+ def _type_convert(self, value):
+ """
+ Converts the value returned by the XMl parse into the equivalent
+ Python type
+ """
+ if value is None:
+ return value
+
+ try:
+ return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
+ except ValueError:
+ pass
+
+ try:
+ return int(value)
+ except ValueError:
+ pass
+
+ try:
+ return decimal.Decimal(value)
+ except decimal.InvalidOperation:
+ pass
+
+ return value
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py
new file mode 100644
index 00000000..655b78a3
--- /dev/null
+++ b/rest_framework/permissions.py
@@ -0,0 +1,114 @@
+"""
+Provides a set of pluggable permission policies.
+"""
+
+
+SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
+
+
+class BasePermission(object):
+ """
+ A base class from which all permission classes should inherit.
+ """
+
+ def has_permission(self, request, view, obj=None):
+ """
+ Return `True` if permission is granted, `False` otherwise.
+ """
+ raise NotImplementedError(".has_permission() must be overridden.")
+
+
+class AllowAny(BasePermission):
+ """
+ Allow any access.
+ This isn't strictly required, since you could use an empty
+ permission_classes list, but it's useful because it makes the intention
+ more explicit.
+ """
+ def has_permission(self, request, view, obj=None):
+ return True
+
+
+class IsAuthenticated(BasePermission):
+ """
+ Allows access only to authenticated users.
+ """
+
+ def has_permission(self, request, view, obj=None):
+ if request.user and request.user.is_authenticated():
+ return True
+ return False
+
+
+class IsAdminUser(BasePermission):
+ """
+ Allows access only to admin users.
+ """
+
+ def has_permission(self, request, view, obj=None):
+ if request.user and request.user.is_staff:
+ return True
+ return False
+
+
+class IsAuthenticatedOrReadOnly(BasePermission):
+ """
+ The request is authenticated as a user, or is a read-only request.
+ """
+
+ def has_permission(self, request, view, obj=None):
+ if (request.method in SAFE_METHODS or
+ request.user and
+ request.user.is_authenticated()):
+ return True
+ return False
+
+
+class DjangoModelPermissions(BasePermission):
+ """
+ The request is authenticated using `django.contrib.auth` permissions.
+ See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
+
+ It ensures that the user is authenticated, and has the appropriate
+ `add`/`change`/`delete` permissions on the model.
+
+ This permission will only be applied against view classes that
+ provide a `.model` attribute, such as the generic class-based views.
+ """
+
+ # Map methods into required permission codes.
+ # Override this if you need to also provide 'view' permissions,
+ # or if you want to provide custom permission codes.
+ perms_map = {
+ 'GET': [],
+ 'OPTIONS': [],
+ 'HEAD': [],
+ 'POST': ['%(app_label)s.add_%(model_name)s'],
+ 'PUT': ['%(app_label)s.change_%(model_name)s'],
+ 'PATCH': ['%(app_label)s.change_%(model_name)s'],
+ 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
+ }
+
+ def get_required_permissions(self, method, model_cls):
+ """
+ Given a model and an HTTP method, return the list of permission
+ codes that the user is required to have.
+ """
+ kwargs = {
+ 'app_label': model_cls._meta.app_label,
+ 'model_name': model_cls._meta.module_name
+ }
+ return [perm % kwargs for perm in self.perms_map[method]]
+
+ def has_permission(self, request, view, obj=None):
+ model_cls = getattr(view, 'model', None)
+ if not model_cls:
+ return True
+
+ perms = self.get_required_permissions(request.method, model_cls)
+
+ if (request.user and
+ request.user.is_authenticated() and
+ request.user.has_perms(perms, obj)):
+ return True
+ return False
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
new file mode 100644
index 00000000..8dff0c77
--- /dev/null
+++ b/rest_framework/renderers.py
@@ -0,0 +1,452 @@
+"""
+Renderers are used to serialize a response into specific media types.
+
+They give us a generic way of being able to handle various media types
+on the response, such as JSON encoded data or HTML output.
+
+REST framework also provides an HTML renderer the renders the browseable API.
+"""
+import copy
+import string
+from django import forms
+from django.http.multipartparser import parse_header
+from django.template import RequestContext, loader
+from django.utils import simplejson as json
+from rest_framework.compat import yaml
+from rest_framework.exceptions import ConfigurationError
+from rest_framework.settings import api_settings
+from rest_framework.request import clone_request
+from rest_framework.utils import dict2xml
+from rest_framework.utils import encoders
+from rest_framework.utils.breadcrumbs import get_breadcrumbs
+from rest_framework import VERSION
+from rest_framework import serializers, parsers
+
+
+class BaseRenderer(object):
+ """
+ All renderers should extend this class, setting the `media_type`
+ and `format` attributes, and override the `.render()` method.
+ """
+
+ media_type = None
+ format = None
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ raise NotImplemented('Renderer class requires .render() to be implemented')
+
+
+class JSONRenderer(BaseRenderer):
+ """
+ Renderer which serializes to json.
+ """
+
+ media_type = 'application/json'
+ format = 'json'
+ encoder_class = encoders.JSONEncoder
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Render `obj` into json.
+ """
+ if data is None:
+ return ''
+
+ # If 'indent' is provided in the context, then pretty print the result.
+ # E.g. If we're being called by the BrowseableAPIRenderer.
+ renderer_context = renderer_context or {}
+ indent = renderer_context.get('indent', None)
+
+ if accepted_media_type:
+ # If the media type looks like 'application/json; indent=4',
+ # then pretty print the result.
+ base_media_type, params = parse_header(accepted_media_type)
+ indent = params.get('indent', indent)
+ try:
+ indent = max(min(int(indent), 8), 0)
+ except (ValueError, TypeError):
+ indent = None
+
+ return json.dumps(data, cls=self.encoder_class, indent=indent)
+
+
+class JSONPRenderer(JSONRenderer):
+ """
+ Renderer which serializes to json,
+ wrapping the json output in a callback function.
+ """
+
+ media_type = 'application/javascript'
+ format = 'jsonp'
+ callback_parameter = 'callback'
+ default_callback = 'callback'
+
+ def get_callback(self, renderer_context):
+ """
+ Determine the name of the callback to wrap around the json output.
+ """
+ request = renderer_context.get('request', None)
+ params = request and request.GET or {}
+ return params.get(self.callback_parameter, self.default_callback)
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Renders into jsonp, wrapping the json output in a callback function.
+
+ Clients may set the callback function name using a query parameter
+ on the URL, for example: ?callback=exampleCallbackName
+ """
+ renderer_context = renderer_context or {}
+ callback = self.get_callback(renderer_context)
+ json = super(JSONPRenderer, self).render(data, accepted_media_type,
+ renderer_context)
+ return "%s(%s);" % (callback, json)
+
+
+class XMLRenderer(BaseRenderer):
+ """
+ Renderer which serializes to XML.
+ """
+
+ media_type = 'application/xml'
+ format = 'xml'
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Renders *obj* into serialized XML.
+ """
+ if data is None:
+ return ''
+ return dict2xml(data)
+
+
+class YAMLRenderer(BaseRenderer):
+ """
+ Renderer which serializes to YAML.
+ """
+
+ media_type = 'application/yaml'
+ format = 'yaml'
+ encoder = encoders.SafeDumper
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Renders *obj* into serialized YAML.
+ """
+ if data is None:
+ return ''
+
+ return yaml.dump(data, stream=None, Dumper=self.encoder)
+
+
+class TemplateHTMLRenderer(BaseRenderer):
+ """
+ An HTML renderer for use with templates.
+
+ The data supplied to the Response object should be a dictionary that will
+ be used as context for the template.
+
+ The template name is determined by (in order of preference):
+
+ 1. An explicit `.template_name` attribute set on the response.
+ 2. An explicit `.template_name` attribute set on this class.
+ 3. The return result of calling `view.get_template_names()`.
+
+ For example:
+ data = {'users': User.objects.all()}
+ return Response(data, template_name='users.html')
+
+ For pre-rendered HTML, see StaticHTMLRenderer.
+ """
+
+ media_type = 'text/html'
+ format = 'html'
+ template_name = None
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Renders data to HTML, using Django's standard template rendering.
+
+ The template name is determined by (in order of preference):
+
+ 1. An explicit .template_name set on the response.
+ 2. An explicit .template_name set on this class.
+ 3. The return result of calling view.get_template_names().
+ """
+ renderer_context = renderer_context or {}
+ view = renderer_context['view']
+ request = renderer_context['request']
+ response = renderer_context['response']
+
+ template_names = self.get_template_names(response, view)
+ template = self.resolve_template(template_names)
+ context = self.resolve_context(data, request)
+ return template.render(context)
+
+ def resolve_template(self, template_names):
+ return loader.select_template(template_names)
+
+ def resolve_context(self, data, request):
+ return RequestContext(request, data)
+
+ def get_template_names(self, response, view):
+ if response.template_name:
+ return [response.template_name]
+ elif self.template_name:
+ return [self.template_name]
+ elif hasattr(view, 'get_template_names'):
+ return view.get_template_names()
+ raise ConfigurationError('Returned a template response with no template_name')
+
+
+class StaticHTMLRenderer(BaseRenderer):
+ """
+ An HTML renderer class that simply returns pre-rendered HTML.
+
+ The data supplied to the Response object should be a string representing
+ the pre-rendered HTML content.
+
+ For example:
+ data = '<html><body>example</body></html>'
+ return Response(data)
+
+ For template rendered HTML, see TemplateHTMLRenderer.
+ """
+ media_type = 'text/html'
+ format = 'html'
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ return data
+
+
+class BrowsableAPIRenderer(BaseRenderer):
+ """
+ HTML renderer used to self-document the API.
+ """
+ media_type = 'text/html'
+ format = 'api'
+ template = 'rest_framework/api.html'
+
+ def get_default_renderer(self, view):
+ """
+ Return an instance of the first valid renderer.
+ (Don't use another documenting renderer.)
+ """
+ renderers = [renderer for renderer in view.renderer_classes
+ if not issubclass(renderer, BrowsableAPIRenderer)]
+ if not renderers:
+ return None
+ return renderers[0]()
+
+ def get_content(self, renderer, data,
+ accepted_media_type, renderer_context):
+ """
+ Get the content as if it had been rendered by the default
+ non-documenting renderer.
+ """
+ if not renderer:
+ return '[No renderers were found]'
+
+ renderer_context['indent'] = 4
+ content = renderer.render(data, accepted_media_type, renderer_context)
+
+ if not all(char in string.printable for char in content):
+ return '[%d bytes of binary content]'
+
+ return content
+
+ def show_form_for_method(self, view, method, request, obj):
+ """
+ Returns True if a form should be shown for this method.
+ """
+ if not method in view.allowed_methods:
+ return # Not a valid method
+
+ if not api_settings.FORM_METHOD_OVERRIDE:
+ return # Cannot use form overloading
+
+ request = clone_request(request, method)
+ try:
+ if not view.has_permission(request, obj):
+ return # Don't have permission
+ except:
+ return # Don't have permission and exception explicitly raise
+ return True
+
+ def serializer_to_form_fields(self, serializer):
+ field_mapping = {
+ serializers.FloatField: forms.FloatField,
+ serializers.IntegerField: forms.IntegerField,
+ serializers.DateTimeField: forms.DateTimeField,
+ serializers.DateField: forms.DateField,
+ serializers.EmailField: forms.EmailField,
+ serializers.CharField: forms.CharField,
+ serializers.BooleanField: forms.BooleanField,
+ serializers.PrimaryKeyRelatedField: forms.ModelChoiceField,
+ serializers.ManyPrimaryKeyRelatedField: forms.ModelMultipleChoiceField,
+ serializers.HyperlinkedRelatedField: forms.ModelChoiceField,
+ serializers.ManyHyperlinkedRelatedField: forms.ModelMultipleChoiceField
+ }
+
+ fields = {}
+ for k, v in serializer.get_fields(True).items():
+ if getattr(v, 'read_only', True):
+ continue
+
+ kwargs = {}
+ kwargs['required'] = v.required
+
+ if getattr(v, 'queryset', None):
+ kwargs['queryset'] = v.queryset
+
+ if getattr(v, 'widget', None):
+ widget = copy.deepcopy(v.widget)
+ # If choices have friendly readable names,
+ # then add in the identities too
+ if getattr(widget, 'choices', None):
+ choices = widget.choices
+ if any([ident != desc for (ident, desc) in choices]):
+ choices = [(ident, "%s (%s)" % (desc, ident))
+ for (ident, desc) in choices]
+ widget.choices = choices
+ kwargs['widget'] = widget
+
+ if getattr(v, 'default', None) is not None:
+ kwargs['initial'] = v.default
+
+ kwargs['label'] = k
+
+ try:
+ fields[k] = field_mapping[v.__class__](**kwargs)
+ except KeyError:
+ fields[k] = forms.CharField(**kwargs)
+ return fields
+
+ def get_form(self, view, method, request):
+ """
+ Get a form, possibly bound to either the input or output data.
+ In the absence on of the Resource having an associated form then
+ provide a form that can be used to submit arbitrary content.
+ """
+ obj = getattr(view, 'object', None)
+ if not self.show_form_for_method(view, method, request, obj):
+ return
+
+ if method == 'DELETE' or method == 'OPTIONS':
+ return True # Don't actually need to return a form
+
+ if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes:
+ media_types = [parser.media_type for parser in view.parser_classes]
+ return self.get_generic_content_form(media_types)
+
+ serializer = view.get_serializer(instance=obj)
+ fields = self.serializer_to_form_fields(serializer)
+
+ # Creating an on the fly form see:
+ # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
+ OnTheFlyForm = type("OnTheFlyForm", (forms.Form,), fields)
+ data = (obj is not None) and serializer.data or None
+ form_instance = OnTheFlyForm(data)
+ return form_instance
+
+ def get_generic_content_form(self, media_types):
+ """
+ Returns a form that allows for arbitrary content types to be tunneled
+ via standard HTML forms.
+ (Which are typically application/x-www-form-urlencoded)
+ """
+
+ # If we're not using content overloading there's no point in supplying a generic form,
+ # as the view won't treat the form's value as the content of the request.
+ if not (api_settings.FORM_CONTENT_OVERRIDE
+ and api_settings.FORM_CONTENTTYPE_OVERRIDE):
+ return None
+
+ content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE
+ content_field = api_settings.FORM_CONTENT_OVERRIDE
+ choices = [(media_type, media_type) for media_type in media_types]
+ initial = media_types[0]
+
+ # NB. http://jacobian.org/writing/dynamic-form-generation/
+ class GenericContentForm(forms.Form):
+ def __init__(self):
+ super(GenericContentForm, self).__init__()
+
+ self.fields[content_type_field] = forms.ChoiceField(
+ label='Content Type',
+ choices=choices,
+ initial=initial
+ )
+ self.fields[content_field] = forms.CharField(
+ label='Content',
+ widget=forms.Textarea
+ )
+
+ return GenericContentForm()
+
+ def get_name(self, view):
+ try:
+ return view.get_name()
+ except AttributeError:
+ return view.__doc__
+
+ def get_description(self, view):
+ try:
+ return view.get_description(html=True)
+ except AttributeError:
+ return view.__doc__
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ """
+ Renders *obj* using the :attr:`template` set on the class.
+
+ The context used in the template contains all the information
+ needed to self-document the response to this request.
+ """
+ accepted_media_type = accepted_media_type or ''
+ renderer_context = renderer_context or {}
+
+ view = renderer_context['view']
+ request = renderer_context['request']
+ response = renderer_context['response']
+
+ renderer = self.get_default_renderer(view)
+ content = self.get_content(renderer, data, accepted_media_type, renderer_context)
+
+ put_form = self.get_form(view, 'PUT', request)
+ post_form = self.get_form(view, 'POST', request)
+ delete_form = self.get_form(view, 'DELETE', request)
+ options_form = self.get_form(view, 'OPTIONS', request)
+
+ name = self.get_name(view)
+ description = self.get_description(view)
+ breadcrumb_list = get_breadcrumbs(request.path)
+
+ template = loader.get_template(self.template)
+ context = RequestContext(request, {
+ 'content': content,
+ 'view': view,
+ 'request': request,
+ 'response': response,
+ 'description': description,
+ 'name': name,
+ 'version': VERSION,
+ 'breadcrumblist': breadcrumb_list,
+ 'allowed_methods': view.allowed_methods,
+ 'available_formats': [renderer.format for renderer in view.renderer_classes],
+ 'put_form': put_form,
+ 'post_form': post_form,
+ 'delete_form': delete_form,
+ 'options_form': options_form,
+ 'api_settings': api_settings
+ })
+
+ ret = template.render(context)
+
+ # Munge DELETE Response code to allow us to return content
+ # (Do this *after* we've rendered the template so that we include
+ # the normal deletion response code in the output)
+ if response.status_code == 204:
+ response.status_code = 200
+
+ return ret
diff --git a/rest_framework/request.py b/rest_framework/request.py
new file mode 100644
index 00000000..a1827ba4
--- /dev/null
+++ b/rest_framework/request.py
@@ -0,0 +1,317 @@
+"""
+The :mod:`request` module provides a :class:`Request` class used to wrap the standard `request`
+object received in all the views.
+
+The wrapped request then offers a richer API, in particular :
+
+ - content automatically parsed according to `Content-Type` header,
+ and available as :meth:`.DATA<Request.DATA>`
+ - full support of PUT method, including support for file uploads
+ - form overloading of HTTP method, content type and content
+"""
+from StringIO import StringIO
+
+from django.http.multipartparser import parse_header
+from rest_framework import exceptions
+from rest_framework.settings import api_settings
+
+
+def is_form_media_type(media_type):
+ """
+ Return True if the media type is a valid form media type.
+ """
+ base_media_type, params = parse_header(media_type)
+ return (base_media_type == 'application/x-www-form-urlencoded' or
+ base_media_type == 'multipart/form-data')
+
+
+class Empty(object):
+ """
+ Placeholder for unset attributes.
+ Cannot use `None`, as that may be a valid value.
+ """
+ pass
+
+
+def _hasattr(obj, name):
+ return not getattr(obj, name) is Empty
+
+
+def clone_request(request, method):
+ """
+ Internal helper method to clone a request, replacing with a different
+ HTTP method. Used for checking permissions against other methods.
+ """
+ ret = Request(request._request,
+ request.parsers,
+ request.authenticators,
+ request.parser_context)
+ ret._data = request._data
+ ret._files = request._files
+ ret._content_type = request._content_type
+ ret._stream = request._stream
+ ret._method = method
+ if hasattr(request, '_user'):
+ ret._user = request._user
+ if hasattr(request, '_auth'):
+ ret._auth = request._auth
+ return ret
+
+
+class Request(object):
+ """
+ Wrapper allowing to enhance a standard `HttpRequest` instance.
+
+ Kwargs:
+ - request(HttpRequest). The original request instance.
+ - parsers_classes(list/tuple). The parsers to use for parsing the
+ request content.
+ - authentication_classes(list/tuple). The authentications used to try
+ authenticating the request's user.
+ """
+
+ _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE
+ _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE
+ _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE
+
+ def __init__(self, request, parsers=None, authenticators=None,
+ negotiator=None, parser_context=None):
+ self._request = request
+ self.parsers = parsers or ()
+ self.authenticators = authenticators or ()
+ self.negotiator = negotiator or self._default_negotiator()
+ self.parser_context = parser_context
+ self._data = Empty
+ self._files = Empty
+ self._method = Empty
+ self._content_type = Empty
+ self._stream = Empty
+
+ if self.parser_context is None:
+ self.parser_context = {}
+ self.parser_context['request'] = self
+
+ def _default_negotiator(self):
+ return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
+
+ @property
+ def method(self):
+ """
+ Returns the HTTP method.
+
+ This allows the `method` to be overridden by using a hidden `form`
+ field on a form POST request.
+ """
+ if not _hasattr(self, '_method'):
+ self._load_method_and_content_type()
+ return self._method
+
+ @property
+ def content_type(self):
+ """
+ Returns the content type header.
+
+ This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`,
+ as it allows the content type to be overridden by using a hidden form
+ field on a form POST request.
+ """
+ if not _hasattr(self, '_content_type'):
+ self._load_method_and_content_type()
+ return self._content_type
+
+ @property
+ def stream(self):
+ """
+ Returns an object that may be used to stream the request content.
+ """
+ if not _hasattr(self, '_stream'):
+ self._load_stream()
+ return self._stream
+
+ @property
+ def QUERY_PARAMS(self):
+ """
+ More semantically correct name for request.GET.
+ """
+ return self._request.GET
+
+ @property
+ def DATA(self):
+ """
+ Parses the request body and returns the data.
+
+ Similar to usual behaviour of `request.POST`, except that it handles
+ arbitrary parsers, and also works on methods other than POST (eg PUT).
+ """
+ if not _hasattr(self, '_data'):
+ self._load_data_and_files()
+ return self._data
+
+ @property
+ def FILES(self):
+ """
+ Parses the request body and returns any files uploaded in the request.
+
+ Similar to usual behaviour of `request.FILES`, except that it handles
+ arbitrary parsers, and also works on methods other than POST (eg PUT).
+ """
+ if not _hasattr(self, '_files'):
+ self._load_data_and_files()
+ return self._files
+
+ @property
+ def user(self):
+ """
+ Returns the user associated with the current request, as authenticated
+ by the authentication classes provided to the request.
+ """
+ if not hasattr(self, '_user'):
+ self._user, self._auth = self._authenticate()
+ return self._user
+
+ @property
+ def auth(self):
+ """
+ Returns any non-user authentication information associated with the
+ request, such as an authentication token.
+ """
+ if not hasattr(self, '_auth'):
+ self._user, self._auth = self._authenticate()
+ return self._auth
+
+ def _load_data_and_files(self):
+ """
+ Parses the request content into self.DATA and self.FILES.
+ """
+ if not _hasattr(self, '_content_type'):
+ self._load_method_and_content_type()
+
+ if not _hasattr(self, '_data'):
+ self._data, self._files = self._parse()
+
+ def _load_method_and_content_type(self):
+ """
+ Sets the method and content_type, and then check if they've
+ been overridden.
+ """
+ self._content_type = self.META.get('HTTP_CONTENT_TYPE',
+ self.META.get('CONTENT_TYPE', ''))
+ self._perform_form_overloading()
+ # if the HTTP method was not overloaded, we take the raw HTTP method
+ if not _hasattr(self, '_method'):
+ self._method = self._request.method
+
+ def _load_stream(self):
+ """
+ Return the content body of the request, as a stream.
+ """
+ try:
+ content_length = int(self.META.get('CONTENT_LENGTH',
+ self.META.get('HTTP_CONTENT_LENGTH')))
+ except (ValueError, TypeError):
+ content_length = 0
+
+ if content_length == 0:
+ self._stream = None
+ elif hasattr(self._request, 'read'):
+ self._stream = self._request
+ else:
+ self._stream = StringIO(self.raw_post_data)
+
+ def _perform_form_overloading(self):
+ """
+ If this is a form POST request, then we need to check if the method and
+ content/content_type have been overridden by setting them in hidden
+ form fields or not.
+ """
+
+ USE_FORM_OVERLOADING = (
+ self._METHOD_PARAM or
+ (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM)
+ )
+
+ # We only need to use form overloading on form POST requests.
+ if (not USE_FORM_OVERLOADING
+ or self._request.method != 'POST'
+ or not is_form_media_type(self._content_type)):
+ return
+
+ # At this point we're committed to parsing the request as form data.
+ self._data = self._request.POST
+ self._files = self._request.FILES
+
+ # Method overloading - change the method and remove the param from the content.
+ if (self._METHOD_PARAM and
+ self._METHOD_PARAM in self._data):
+ self._method = self._data[self._METHOD_PARAM].upper()
+
+ # Content overloading - modify the content type, and force re-parse.
+ if (self._CONTENT_PARAM and
+ self._CONTENTTYPE_PARAM and
+ self._CONTENT_PARAM in self._data and
+ self._CONTENTTYPE_PARAM in self._data):
+ self._content_type = self._data[self._CONTENTTYPE_PARAM]
+ self._stream = StringIO(self._data[self._CONTENT_PARAM])
+ self._data, self._files = (Empty, Empty)
+
+ def _parse(self):
+ """
+ Parse the request content, returning a two-tuple of (data, files)
+
+ May raise an `UnsupportedMediaType`, or `ParseError` exception.
+ """
+ stream = self.stream
+ media_type = self.content_type
+
+ if stream is None or media_type is None:
+ return (None, None)
+
+ parser = self.negotiator.select_parser(self, self.parsers)
+
+ if not parser:
+ raise exceptions.UnsupportedMediaType(media_type)
+
+ parsed = parser.parse(stream, media_type, self.parser_context)
+
+ # Parser classes may return the raw data, or a
+ # DataAndFiles object. Unpack the result as required.
+ try:
+ return (parsed.data, parsed.files)
+ except AttributeError:
+ return (parsed, None)
+
+ def _authenticate(self):
+ """
+ Attempt to authenticate the request using each authentication instance in turn.
+ Returns a two-tuple of (user, authtoken).
+ """
+ for authenticator in self.authenticators:
+ user_auth_tuple = authenticator.authenticate(self)
+ if not user_auth_tuple is None:
+ return user_auth_tuple
+ return self._not_authenticated()
+
+ def _not_authenticated(self):
+ """
+ Return a two-tuple of (user, authtoken), representing an
+ unauthenticated request.
+
+ By default this will be (AnonymousUser, None).
+ """
+ if api_settings.UNAUTHENTICATED_USER:
+ user = api_settings.UNAUTHENTICATED_USER()
+ else:
+ user = None
+
+ if api_settings.UNAUTHENTICATED_TOKEN:
+ auth = api_settings.UNAUTHENTICATED_TOKEN()
+ else:
+ auth = None
+
+ return (user, auth)
+
+ def __getattr__(self, attr):
+ """
+ Proxy other attributes to the underlying HttpRequest object.
+ """
+ return getattr(self._request, attr)
diff --git a/rest_framework/response.py b/rest_framework/response.py
new file mode 100644
index 00000000..7a459c8f
--- /dev/null
+++ b/rest_framework/response.py
@@ -0,0 +1,47 @@
+from django.core.handlers.wsgi import STATUS_CODE_TEXT
+from django.template.response import SimpleTemplateResponse
+
+
+class Response(SimpleTemplateResponse):
+ """
+ An HttpResponse that allows it's data to be rendered into
+ arbitrary media types.
+ """
+
+ def __init__(self, data=None, status=200,
+ template_name=None, headers=None):
+ """
+ Alters the init arguments slightly.
+ For example, drop 'template_name', and instead use 'data'.
+
+ Setting 'renderer' and 'media_type' will typically be defered,
+ For example being set automatically by the `APIView`.
+ """
+ super(Response, self).__init__(None, status=status)
+ self.data = data
+ self.headers = headers and headers[:] or []
+ self.template_name = template_name
+
+ @property
+ def rendered_content(self):
+ renderer = getattr(self, 'accepted_renderer', None)
+ media_type = getattr(self, 'accepted_media_type', None)
+ context = getattr(self, 'renderer_context', None)
+
+ assert renderer, ".accepted_renderer not set on Response"
+ assert media_type, ".accepted_media_type not set on Response"
+ assert context, ".renderer_context not set on Response"
+ context['response'] = self
+
+ self['Content-Type'] = media_type
+ return renderer.render(self.data, media_type, context)
+
+ @property
+ def status_text(self):
+ """
+ Returns reason text corresponding to our HTTP response status code.
+ Provided for convenience.
+ """
+ # TODO: Deprecate and use a template tag instead
+ # TODO: Status code text for RFC 6585 status codes
+ return STATUS_CODE_TEXT.get(self.status_code, '')
diff --git a/djangorestframework/reverse.py b/rest_framework/reverse.py
index ba663f98..c9db02f0 100644
--- a/djangorestframework/reverse.py
+++ b/rest_framework/reverse.py
@@ -5,13 +5,15 @@ from django.core.urlresolvers import reverse as django_reverse
from django.utils.functional import lazy
-def reverse(viewname, *args, **kwargs):
+def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
"""
Same as `django.core.urlresolvers.reverse`, but optionally takes a request
and returns a fully qualified URL, using the request to get the base URL.
"""
- request = kwargs.pop('request', None)
- url = django_reverse(viewname, *args, **kwargs)
+ if format is not None:
+ kwargs = kwargs or {}
+ kwargs['format'] = format
+ url = django_reverse(viewname, args=args, kwargs=kwargs, **extra)
if request:
return request.build_absolute_uri(url)
return url
diff --git a/examples/blogpost/__init__.py b/rest_framework/runtests/__init__.py
index e69de29b..e69de29b 100644
--- a/examples/blogpost/__init__.py
+++ b/rest_framework/runtests/__init__.py
diff --git a/djangorestframework/runtests/runcoverage.py b/rest_framework/runtests/runcoverage.py
index 0ab61479..ea2e3d45 100644..100755
--- a/djangorestframework/runtests/runcoverage.py
+++ b/rest_framework/runtests/runcoverage.py
@@ -1,5 +1,6 @@
+#!/usr/bin/env python
"""
-Useful tool to run the test suite for djangorestframework and generate a coverage report.
+Useful tool to run the test suite for rest_framework and generate a coverage report.
"""
# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
@@ -7,13 +8,13 @@ Useful tool to run the test suite for djangorestframework and generate a coverag
# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
import os
import sys
-os.environ['DJANGO_SETTINGS_MODULE'] = 'djangorestframework.runtests.settings'
+os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings'
from coverage import coverage
-from itertools import chain
+
def main():
- """Run the tests for djangorestframework and generate a coverage report."""
+ """Run the tests for rest_framework and generate a coverage report."""
cov = coverage()
cov.erase()
@@ -31,21 +32,21 @@ def main():
'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.',
DeprecationWarning
)
- failures = TestRunner(['djangorestframework'])
+ failures = TestRunner(['rest_framework'])
else:
test_runner = TestRunner()
- failures = test_runner.run_tests(['djangorestframework'])
+ failures = test_runner.run_tests(['rest_framework'])
cov.stop()
# Discover the list of all modules that we should test coverage for
- import djangorestframework
+ import rest_framework
- project_dir = os.path.dirname(djangorestframework.__file__)
+ project_dir = os.path.dirname(rest_framework.__file__)
cov_files = []
for (path, dirs, files) in os.walk(project_dir):
# Drop tests and runtests directories from the test coverage report
- if os.path.basename(path) == 'tests' or os.path.basename(path) == 'runtests':
+ if os.path.basename(path) in ['tests', 'runtests', 'migrations']:
continue
# Drop the compat module from coverage, since we're not interested in the coverage
@@ -57,7 +58,8 @@ def main():
cov_files.extend([os.path.join(path, file) for file in files if file.endswith('.py')])
cov.report(cov_files)
- cov.xml_report(cov_files)
+ if '--html' in sys.argv:
+ cov.html_report(cov_files, directory='coverage')
sys.exit(failures)
if __name__ == '__main__':
diff --git a/djangorestframework/runtests/runtests.py b/rest_framework/runtests/runtests.py
index 9f5cc7aa..1bd0a5fc 100644..100755
--- a/djangorestframework/runtests/runtests.py
+++ b/rest_framework/runtests/runtests.py
@@ -1,18 +1,16 @@
-'''
-Created on Mar 10, 2011
+#!/usr/bin/env python
-@author: tomchristie
-'''
# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
# http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/
# http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
import os
import sys
-os.environ['DJANGO_SETTINGS_MODULE'] = 'djangorestframework.runtests.settings'
+os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings'
from django.conf import settings
from django.test.utils import get_runner
+
def usage():
return """
Usage: python runtests.py [UnitTestClass].[method]
@@ -22,6 +20,7 @@ def usage():
Append a method name if you only want to test a specific method of that class.
"""
+
def main():
TestRunner = get_runner(settings)
@@ -33,7 +32,7 @@ def main():
else:
print usage()
sys.exit(1)
- failures = test_runner.run_tests(['djangorestframework' + test_case])
+ failures = test_runner.run_tests(['tests' + test_case])
sys.exit(failures)
diff --git a/djangorestframework/runtests/settings.py b/rest_framework/runtests/settings.py
index 7cb3e27b..951b1e72 100644
--- a/djangorestframework/runtests/settings.py
+++ b/rest_framework/runtests/settings.py
@@ -89,7 +89,9 @@ INSTALLED_APPS = (
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
- 'djangorestframework',
+ 'rest_framework',
+ 'rest_framework.authtoken',
+ 'rest_framework.tests'
)
STATIC_URL = '/static/'
@@ -99,15 +101,6 @@ import django
if django.VERSION < (1, 3):
INSTALLED_APPS += ('staticfiles',)
-
-# OAuth support is optional, so we only test oauth if it's installed.
-try:
- import oauth_provider
-except ImportError:
- pass
-else:
- INSTALLED_APPS += ('oauth_provider',)
-
# If we're running on the Jenkins server we want to archive the coverage reports as XML.
import os
if os.environ.get('HUDSON_URL', None):
diff --git a/djangorestframework/runtests/urls.py b/rest_framework/runtests/urls.py
index 4b7da787..4b7da787 100644
--- a/djangorestframework/runtests/urls.py
+++ b/rest_framework/runtests/urls.py
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
new file mode 100644
index 00000000..3d134a74
--- /dev/null
+++ b/rest_framework/serializers.py
@@ -0,0 +1,513 @@
+import copy
+import datetime
+import types
+from decimal import Decimal
+from django.db import models
+from django.forms import widgets
+from django.utils.datastructures import SortedDict
+from rest_framework.compat import get_concrete_model
+from rest_framework.fields import *
+
+
+class DictWithMetadata(dict):
+ """
+ A dict-like object, that can have additional properties attached.
+ """
+ pass
+
+
+class SortedDictWithMetadata(SortedDict, DictWithMetadata):
+ """
+ A sorted dict-like object, that can have additional properties attached.
+ """
+ pass
+
+
+def _is_protected_type(obj):
+ """
+ True if the object is a native datatype that does not need to
+ be serialized further.
+ """
+ return isinstance(obj, (
+ types.NoneType,
+ int, long,
+ datetime.datetime, datetime.date, datetime.time,
+ float, Decimal,
+ basestring)
+ )
+
+
+def _get_declared_fields(bases, attrs):
+ """
+ Create a list of serializer field instances from the passed in 'attrs',
+ plus any fields on the base classes (in 'bases').
+
+ Note that all fields from the base classes are used.
+ """
+ fields = [(field_name, attrs.pop(field_name))
+ for field_name, obj in attrs.items()
+ if isinstance(obj, Field)]
+ fields.sort(key=lambda x: x[1].creation_counter)
+
+ # If this class is subclassing another Serializer, add that Serializer's
+ # fields. Note that we loop over the bases in *reverse*. This is necessary
+ # in order to the correct order of fields.
+ for base in bases[::-1]:
+ if hasattr(base, 'base_fields'):
+ fields = base.base_fields.items() + fields
+
+ return SortedDict(fields)
+
+
+class SerializerMetaclass(type):
+ def __new__(cls, name, bases, attrs):
+ attrs['base_fields'] = _get_declared_fields(bases, attrs)
+ return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
+class SerializerOptions(object):
+ """
+ Meta class options for Serializer
+ """
+ def __init__(self, meta):
+ self.depth = getattr(meta, 'depth', 0)
+ self.fields = getattr(meta, 'fields', ())
+ self.exclude = getattr(meta, 'exclude', ())
+
+
+class BaseSerializer(Field):
+ class Meta(object):
+ pass
+
+ _options_class = SerializerOptions
+ _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations.
+
+ def __init__(self, data=None, instance=None, context=None, **kwargs):
+ super(BaseSerializer, self).__init__(**kwargs)
+ self.fields = copy.deepcopy(self.base_fields)
+ self.opts = self._options_class(self.Meta)
+ self.parent = None
+ self.root = None
+
+ self.context = context or {}
+
+ self.init_data = data
+ self.object = instance
+
+ self._data = None
+ self._errors = None
+
+ #####
+ # Methods to determine which fields to use when (de)serializing objects.
+
+ def default_fields(self, serialize, obj=None, data=None, nested=False):
+ """
+ Return the complete set of default fields for the object, as a dict.
+ """
+ return {}
+
+ def get_fields(self, serialize, obj=None, data=None, nested=False):
+ """
+ Returns the complete set of fields for the object as a dict.
+
+ This will be the set of any explicitly declared fields,
+ plus the set of fields returned by default_fields().
+ """
+ ret = SortedDict()
+
+ # Get the explicitly declared fields
+ for key, field in self.fields.items():
+ ret[key] = field
+ # Set up the field
+ field.initialize(parent=self)
+
+ # Add in the default fields
+ fields = self.default_fields(serialize, obj, data, nested)
+ for key, val in fields.items():
+ if key not in ret:
+ ret[key] = val
+
+ # If 'fields' is specified, use those fields, in that order.
+ if self.opts.fields:
+ new = SortedDict()
+ for key in self.opts.fields:
+ new[key] = ret[key]
+ ret = new
+
+ # Remove anything in 'exclude'
+ if self.opts.exclude:
+ for key in self.opts.exclude:
+ ret.pop(key, None)
+
+ return ret
+
+ #####
+ # Field methods - used when the serializer class is itself used as a field.
+
+ def initialize(self, parent):
+ """
+ Same behaviour as usual Field, except that we need to keep track
+ of state so that we can deal with handling maximum depth.
+ """
+ super(BaseSerializer, self).initialize(parent)
+ if parent.opts.depth:
+ self.opts.depth = parent.opts.depth - 1
+
+ #####
+ # Methods to convert or revert from objects <--> primative representations.
+
+ def get_field_key(self, field_name):
+ """
+ Return the key that should be used for a given field.
+ """
+ return field_name
+
+ def convert_object(self, obj):
+ """
+ Core of serialization.
+ Convert an object into a dictionary of serialized field values.
+ """
+ ret = self._dict_class()
+ ret.fields = {}
+
+ fields = self.get_fields(serialize=True, obj=obj, nested=bool(self.opts.depth))
+ for field_name, field in fields.items():
+ key = self.get_field_key(field_name)
+ value = field.field_to_native(obj, field_name)
+ ret[key] = value
+ ret.fields[key] = field
+ return ret
+
+ def restore_fields(self, data):
+ """
+ Core of deserialization, together with `restore_object`.
+ Converts a dictionary of data into a dictionary of deserialized fields.
+ """
+ fields = self.get_fields(serialize=False, data=data, nested=bool(self.opts.depth))
+ reverted_data = {}
+ for field_name, field in fields.items():
+ try:
+ field.field_from_native(data, field_name, reverted_data)
+ except ValidationError as err:
+ self._errors[field_name] = list(err.messages)
+
+ return reverted_data
+
+ def perform_validation(self, attrs):
+ """
+ Run `validate_<fieldname>()` and `validate()` methods on the serializer
+ """
+ # TODO: refactor this so we're not determining the fields again
+ fields = self.get_fields(serialize=False, data=attrs, nested=bool(self.opts.depth))
+
+ for field_name, field in fields.items():
+ try:
+ validate_method = getattr(self, 'validate_%s' % field_name, None)
+ if validate_method:
+ source = field.source or field_name
+ attrs = validate_method(attrs, source)
+ except ValidationError as err:
+ self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages)
+
+ try:
+ attrs = self.validate(attrs)
+ except ValidationError as err:
+ self._errors['non_field_errors'] = err.messages
+
+ return attrs
+
+ def validate(self, attrs):
+ """
+ Stub method, to be overridden in Serializer subclasses
+ """
+ return attrs
+
+ def restore_object(self, attrs, instance=None):
+ """
+ Deserialize a dictionary of attributes into an object instance.
+ You should override this method to control how deserialized objects
+ are instantiated.
+ """
+ if instance is not None:
+ instance.update(attrs)
+ return instance
+ return attrs
+
+ def to_native(self, obj):
+ """
+ Serialize objects -> primatives.
+ """
+ if isinstance(obj, dict):
+ return dict([(key, self.to_native(val))
+ for (key, val) in obj.items()])
+ elif hasattr(obj, '__iter__'):
+ return [self.to_native(item) for item in obj]
+ return self.convert_object(obj)
+
+ def from_native(self, data):
+ """
+ Deserialize primatives -> objects.
+ """
+ if hasattr(data, '__iter__') and not isinstance(data, dict):
+ # TODO: error data when deserializing lists
+ return (self.from_native(item) for item in data)
+
+ self._errors = {}
+ if data is not None:
+ attrs = self.restore_fields(data)
+ attrs = self.perform_validation(attrs)
+ else:
+ self._errors['non_field_errors'] = ['No input provided']
+
+ if not self._errors:
+ return self.restore_object(attrs, instance=getattr(self, 'object', None))
+
+ def field_to_native(self, obj, field_name):
+ """
+ Override default so that we can apply ModelSerializer as a nested
+ field to relationships.
+ """
+ obj = getattr(obj, self.source or field_name)
+
+ # If the object has an "all" method, assume it's a relationship
+ if is_simple_callable(getattr(obj, 'all', None)):
+ return [self.to_native(item) for item in obj.all()]
+
+ return self.to_native(obj)
+
+ @property
+ def errors(self):
+ """
+ Run deserialization and return error data,
+ setting self.object if no errors occurred.
+ """
+ if self._errors is None:
+ obj = self.from_native(self.init_data)
+ if not self._errors:
+ self.object = obj
+ return self._errors
+
+ def is_valid(self):
+ return not self.errors
+
+ @property
+ def data(self):
+ if self._data is None:
+ self._data = self.to_native(self.object)
+ return self._data
+
+ def save(self):
+ """
+ Save the deserialized object and return it.
+ """
+ self.object.save()
+ return self.object
+
+
+class Serializer(BaseSerializer):
+ __metaclass__ = SerializerMetaclass
+
+
+class ModelSerializerOptions(SerializerOptions):
+ """
+ Meta class options for ModelSerializer
+ """
+ def __init__(self, meta):
+ super(ModelSerializerOptions, self).__init__(meta)
+ self.model = getattr(meta, 'model', None)
+
+
+class ModelSerializer(Serializer):
+ """
+ A serializer that deals with model instances and querysets.
+ """
+ _options_class = ModelSerializerOptions
+
+ def default_fields(self, serialize, obj=None, data=None, nested=False):
+ """
+ Return all the fields that should be serialized for the model.
+ """
+ # TODO: Modfiy this so that it's called on init, and drop
+ # serialize/obj/data arguments.
+ #
+ # We *could* provide a hook for dynamic fields, but
+ # it'd be nice if the default was to generate fields statically
+ # at the point of __init__
+
+ cls = self.opts.model
+ opts = get_concrete_model(cls)._meta
+ pk_field = opts.pk
+ while pk_field.rel:
+ pk_field = pk_field.rel.to._meta.pk
+ fields = [pk_field]
+ fields += [field for field in opts.fields if field.serialize]
+ fields += [field for field in opts.many_to_many if field.serialize]
+
+ ret = SortedDict()
+ is_pk = True # First field in the list is the pk
+
+ for model_field in fields:
+ if is_pk:
+ field = self.get_pk_field(model_field)
+ is_pk = False
+ elif model_field.rel and nested:
+ field = self.get_nested_field(model_field)
+ elif model_field.rel:
+ to_many = isinstance(model_field,
+ models.fields.related.ManyToManyField)
+ field = self.get_related_field(model_field, to_many=to_many)
+ else:
+ field = self.get_field(model_field)
+
+ if field:
+ field.initialize(parent=self)
+ ret[model_field.name] = field
+
+ return ret
+
+ def get_pk_field(self, model_field):
+ """
+ Returns a default instance of the pk field.
+ """
+ return Field()
+
+ def get_nested_field(self, model_field):
+ """
+ Creates a default instance of a nested relational field.
+ """
+ return ModelSerializer()
+
+ def get_related_field(self, model_field, to_many=False):
+ """
+ Creates a default instance of a flat relational field.
+ """
+ # TODO: filter queryset using:
+ # .using(db).complex_filter(self.rel.limit_choices_to)
+ queryset = model_field.rel.to._default_manager
+ if to_many:
+ return ManyPrimaryKeyRelatedField(queryset=queryset)
+ return PrimaryKeyRelatedField(queryset=queryset)
+
+ def get_field(self, model_field):
+ """
+ Creates a default instance of a basic non-relational field.
+ """
+ kwargs = {}
+
+ kwargs['blank'] = model_field.blank
+
+ if model_field.null:
+ kwargs['required'] = False
+
+ if model_field.has_default():
+ kwargs['required'] = False
+ kwargs['default'] = model_field.get_default()
+
+ if model_field.__class__ == models.TextField:
+ kwargs['widget'] = widgets.Textarea
+
+ # TODO: TypedChoiceField?
+ if model_field.flatchoices: # This ModelField contains choices
+ kwargs['choices'] = model_field.flatchoices
+ return ChoiceField(**kwargs)
+
+ field_mapping = {
+ models.FloatField: FloatField,
+ models.IntegerField: IntegerField,
+ models.PositiveIntegerField: IntegerField,
+ models.SmallIntegerField: IntegerField,
+ models.PositiveSmallIntegerField: IntegerField,
+ models.DateTimeField: DateTimeField,
+ models.DateField: DateField,
+ models.EmailField: EmailField,
+ models.CharField: CharField,
+ models.TextField: CharField,
+ models.CommaSeparatedIntegerField: CharField,
+ models.BooleanField: BooleanField,
+ }
+ try:
+ return field_mapping[model_field.__class__](**kwargs)
+ except KeyError:
+ return ModelField(model_field=model_field, **kwargs)
+
+ def restore_object(self, attrs, instance=None):
+ """
+ Restore the model instance.
+ """
+ self.m2m_data = {}
+
+ if instance:
+ for key, val in attrs.items():
+ setattr(instance, key, val)
+ return instance
+
+ for field in self.opts.model._meta.many_to_many:
+ if field.name in attrs:
+ self.m2m_data[field.name] = attrs.pop(field.name)
+ return self.opts.model(**attrs)
+
+ def save(self, save_m2m=True):
+ """
+ Save the deserialized object and return it.
+ """
+ self.object.save()
+
+ if self.m2m_data and save_m2m:
+ for accessor_name, object_list in self.m2m_data.items():
+ setattr(self.object, accessor_name, object_list)
+ self.m2m_data = {}
+
+ return self.object
+
+
+class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
+ """
+ Options for HyperlinkedModelSerializer
+ """
+ def __init__(self, meta):
+ super(HyperlinkedModelSerializerOptions, self).__init__(meta)
+ self.view_name = getattr(meta, 'view_name', None)
+
+
+class HyperlinkedModelSerializer(ModelSerializer):
+ """
+ """
+ _options_class = HyperlinkedModelSerializerOptions
+ _default_view_name = '%(model_name)s-detail'
+
+ url = HyperlinkedIdentityField()
+
+ def __init__(self, *args, **kwargs):
+ super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs)
+ if self.opts.view_name is None:
+ self.opts.view_name = self._get_default_view_name(self.opts.model)
+
+ def _get_default_view_name(self, model):
+ """
+ Return the view name to use if 'view_name' is not specified in 'Meta'
+ """
+ model_meta = model._meta
+ format_kwargs = {
+ 'app_label': model_meta.app_label,
+ 'model_name': model_meta.object_name.lower()
+ }
+ return self._default_view_name % format_kwargs
+
+ def get_pk_field(self, model_field):
+ return None
+
+ def get_related_field(self, model_field, to_many):
+ """
+ Creates a default instance of a flat relational field.
+ """
+ # TODO: filter queryset using:
+ # .using(db).complex_filter(self.rel.limit_choices_to)
+ rel = model_field.rel.to
+ queryset = rel._default_manager
+ kwargs = {
+ 'queryset': queryset,
+ 'view_name': self._get_default_view_name(rel)
+ }
+ if to_many:
+ return ManyHyperlinkedRelatedField(**kwargs)
+ return HyperlinkedRelatedField(**kwargs)
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
new file mode 100644
index 00000000..9c40a214
--- /dev/null
+++ b/rest_framework/settings.py
@@ -0,0 +1,149 @@
+"""
+Settings for REST framework are all namespaced in the REST_FRAMEWORK setting.
+For example your project's `settings.py` file might look like this:
+
+REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.YAMLRenderer',
+ )
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework.parsers.JSONParser',
+ 'rest_framework.parsers.YAMLParser',
+ )
+}
+
+This module provides the `api_setting` object, that is used to access
+REST framework settings, checking for user settings first, then falling
+back to the defaults.
+"""
+from django.conf import settings
+from django.utils import importlib
+
+
+USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None)
+
+DEFAULTS = {
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ ),
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework.parsers.JSONParser',
+ 'rest_framework.parsers.FormParser',
+ 'rest_framework.parsers.MultiPartParser'
+ ),
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework.authentication.BasicAuthentication'
+ ),
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.AllowAny',
+ ),
+ 'DEFAULT_THROTTLE_CLASSES': (
+ ),
+
+ 'DEFAULT_CONTENT_NEGOTIATION_CLASS':
+ 'rest_framework.negotiation.DefaultContentNegotiation',
+ 'DEFAULT_MODEL_SERIALIZER_CLASS':
+ 'rest_framework.serializers.ModelSerializer',
+ 'DEFAULT_PAGINATION_SERIALIZER_CLASS':
+ 'rest_framework.pagination.PaginationSerializer',
+
+ 'DEFAULT_THROTTLE_RATES': {
+ 'user': None,
+ 'anon': None,
+ },
+ 'PAGINATE_BY': None,
+
+ 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
+ 'UNAUTHENTICATED_TOKEN': None,
+
+ 'FORM_METHOD_OVERRIDE': '_method',
+ 'FORM_CONTENT_OVERRIDE': '_content',
+ 'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
+ 'URL_ACCEPT_OVERRIDE': 'accept',
+ 'URL_FORMAT_OVERRIDE': 'format',
+
+ 'FORMAT_SUFFIX_KWARG': 'format'
+}
+
+
+# List of settings that may be in string import notation.
+IMPORT_STRINGS = (
+ 'DEFAULT_RENDERER_CLASSES',
+ 'DEFAULT_PARSER_CLASSES',
+ 'DEFAULT_AUTHENTICATION_CLASSES',
+ 'DEFAULT_PERMISSION_CLASSES',
+ 'DEFAULT_THROTTLE_CLASSES',
+ 'DEFAULT_CONTENT_NEGOTIATION_CLASS',
+ 'DEFAULT_MODEL_SERIALIZER_CLASS',
+ 'DEFAULT_PAGINATION_SERIALIZER_CLASS',
+ 'UNAUTHENTICATED_USER',
+ 'UNAUTHENTICATED_TOKEN',
+)
+
+
+def perform_import(val, setting_name):
+ """
+ If the given setting is a string import notation,
+ then perform the necessary import or imports.
+ """
+ if isinstance(val, basestring):
+ return import_from_string(val, setting_name)
+ elif isinstance(val, (list, tuple)):
+ return [import_from_string(item, setting_name) for item in val]
+ return val
+
+
+def import_from_string(val, setting_name):
+ """
+ Attempt to import a class from a string representation.
+ """
+ try:
+ # Nod to tastypie's use of importlib.
+ parts = val.split('.')
+ module_path, class_name = '.'.join(parts[:-1]), parts[-1]
+ module = importlib.import_module(module_path)
+ return getattr(module, class_name)
+ except:
+ msg = "Could not import '%s' for API setting '%s'" % (val, setting_name)
+ raise ImportError(msg)
+
+
+class APISettings(object):
+ """
+ A settings object, that allows API settings to be accessed as properties.
+ For example:
+
+ from rest_framework.settings import api_settings
+ print api_settings.DEFAULT_RENDERER_CLASSES
+
+ Any setting with string import paths will be automatically resolved
+ and return the class, rather than the string literal.
+ """
+ def __init__(self, user_settings=None, defaults=None, import_strings=None):
+ self.user_settings = user_settings or {}
+ self.defaults = defaults or {}
+ self.import_strings = import_strings or ()
+
+ def __getattr__(self, attr):
+ if attr not in self.defaults.keys():
+ raise AttributeError("Invalid API setting: '%s'" % attr)
+
+ try:
+ # Check if present in user settings
+ val = self.user_settings[attr]
+ except KeyError:
+ # Fall back to defaults
+ val = self.defaults[attr]
+
+ # Coerce import strings into classes
+ if val and attr in self.import_strings:
+ val = perform_import(val, attr)
+
+ # Cache the result
+ setattr(self, attr, val)
+ return val
+
+api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
new file mode 100644
index 00000000..c650ef2e
--- /dev/null
+++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
@@ -0,0 +1,22 @@
+/*
+
+This CSS file contains some tweaks specific to the included Bootstrap theme.
+It's separate from `style.css` so that it can be easily overridden by replacing
+a single block in the template.
+
+*/
+
+
+.form-actions {
+ background: transparent;
+ border-top-color: transparent;
+ padding-top: 0;
+}
+
+.navbar-inverse .brand a {
+ color: #999;
+}
+.navbar-inverse .brand:hover a {
+ color: white;
+ text-decoration: none;
+} \ No newline at end of file
diff --git a/rest_framework/static/rest_framework/css/bootstrap.min.css b/rest_framework/static/rest_framework/css/bootstrap.min.css
new file mode 100644
index 00000000..373f4b43
--- /dev/null
+++ b/rest_framework/static/rest_framework/css/bootstrap.min.css
@@ -0,0 +1,841 @@
+/*!
+ * Bootstrap v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;}
+.clearfix:after{clear:both;}
+.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}
+.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+a:hover,a:active{outline:0;}
+sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}
+#map_canvas img{max-width:none;}
+button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
+button,input{*overflow:visible;line-height:normal;}
+button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
+button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
+input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;}
+input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333333;background-color:#ffffff;}
+a{color:#0088cc;text-decoration:none;}
+a:hover{color:#005580;text-decoration:underline;}
+.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);}
+.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;}
+.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;}
+.row:after{clear:both;}
+[class*="span"]{float:left;min-height:1px;margin-left:20px;}
+.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.span12{width:940px;}
+.span11{width:860px;}
+.span10{width:780px;}
+.span9{width:700px;}
+.span8{width:620px;}
+.span7{width:540px;}
+.span6{width:460px;}
+.span5{width:380px;}
+.span4{width:300px;}
+.span3{width:220px;}
+.span2{width:140px;}
+.span1{width:60px;}
+.offset12{margin-left:980px;}
+.offset11{margin-left:900px;}
+.offset10{margin-left:820px;}
+.offset9{margin-left:740px;}
+.offset8{margin-left:660px;}
+.offset7{margin-left:580px;}
+.offset6{margin-left:500px;}
+.offset5{margin-left:420px;}
+.offset4{margin-left:340px;}
+.offset3{margin-left:260px;}
+.offset2{margin-left:180px;}
+.offset1{margin-left:100px;}
+.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;}
+.row-fluid:after{clear:both;}
+.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;}
+.row-fluid [class*="span"]:first-child{margin-left:0;}
+.row-fluid .span12{width:100%;*width:99.94680851063829%;}
+.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;}
+.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;}
+.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;}
+.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;}
+.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;}
+.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;}
+.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;}
+.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;}
+.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;}
+.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;}
+.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;}
+.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;}
+.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;}
+.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;}
+.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;}
+.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;}
+.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;}
+.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;}
+.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;}
+.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;}
+.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;}
+.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;}
+.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;}
+.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;}
+.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;}
+.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;}
+.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;}
+.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;}
+.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;}
+.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;}
+.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;}
+.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;}
+.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;}
+.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;}
+.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;}
+[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;}
+[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;}
+.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;}
+.container:after{clear:both;}
+.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;}
+.container-fluid:after{clear:both;}
+p{margin:0 0 10px;}
+.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;}
+small{font-size:85%;}
+strong{font-weight:bold;}
+em{font-style:italic;}
+cite{font-style:normal;}
+.muted{color:#999999;}
+.text-warning{color:#c09853;}
+.text-error{color:#b94a48;}
+.text-info{color:#3a87ad;}
+.text-success{color:#468847;}
+h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:1;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999999;}
+h1{font-size:36px;line-height:40px;}
+h2{font-size:30px;line-height:40px;}
+h3{font-size:24px;line-height:40px;}
+h4{font-size:18px;line-height:20px;}
+h5{font-size:14px;line-height:20px;}
+h6{font-size:12px;line-height:20px;}
+h1 small{font-size:24px;}
+h2 small{font-size:18px;}
+h3 small{font-size:14px;}
+h4 small{font-size:14px;}
+.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eeeeee;}
+ul,ol{padding:0;margin:0 0 10px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+li{line-height:20px;}
+ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
+dl{margin-bottom:20px;}
+dt,dd{line-height:20px;}
+dt{font-weight:bold;}
+dd{margin-left:10px;}
+.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;}
+.dl-horizontal:after{clear:both;}
+.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
+.dl-horizontal dd{margin-left:180px;}
+hr{margin:20px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
+abbr[title]{cursor:help;border-bottom:1px dotted #999999;}
+abbr.initialism{font-size:90%;text-transform:uppercase;}
+blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px;}
+blockquote small{display:block;line-height:20px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
+blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
+blockquote.pull-right small:before{content:'';}
+blockquote.pull-right small:after{content:'\00A0 \2014';}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;}
+code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
+pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;}
+pre code{padding:0;color:inherit;background-color:transparent;border:0;}
+.pre-scrollable{max-height:340px;overflow-y:scroll;}
+.label,.badge{font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;}
+.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
+a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;}
+.label-important,.badge-important{background-color:#b94a48;}
+.label-important[href],.badge-important[href]{background-color:#953b39;}
+.label-warning,.badge-warning{background-color:#f89406;}
+.label-warning[href],.badge-warning[href]{background-color:#c67605;}
+.label-success,.badge-success{background-color:#468847;}
+.label-success[href],.badge-success[href]{background-color:#356635;}
+.label-info,.badge-info{background-color:#3a87ad;}
+.label-info[href],.badge-info[href]{background-color:#2d6987;}
+.label-inverse,.badge-inverse{background-color:#333333;}
+.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;}
+.btn .label,.btn .badge{position:relative;top:-1px;}
+.btn-mini .label,.btn-mini .badge{top:0;}
+table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}
+.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
+.table th{font-weight:bold;}
+.table thead th{vertical-align:bottom;}
+.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
+.table tbody+tbody{border-top:2px solid #dddddd;}
+.table-condensed th,.table-condensed td{padding:4px 5px;}
+.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
+.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
+.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;}
+.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;}
+.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;}
+.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child,.table-bordered tfoot:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;}
+.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;}
+.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topleft:4px;}
+.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5;}
+table [class*=span],.row-fluid table [class*=span]{display:table-cell;float:none;margin-left:0;}
+.table .span1{float:none;width:44px;margin-left:0;}
+.table .span2{float:none;width:124px;margin-left:0;}
+.table .span3{float:none;width:204px;margin-left:0;}
+.table .span4{float:none;width:284px;margin-left:0;}
+.table .span5{float:none;width:364px;margin-left:0;}
+.table .span6{float:none;width:444px;margin-left:0;}
+.table .span7{float:none;width:524px;margin-left:0;}
+.table .span8{float:none;width:604px;margin-left:0;}
+.table .span9{float:none;width:684px;margin-left:0;}
+.table .span10{float:none;width:764px;margin-left:0;}
+.table .span11{float:none;width:844px;margin-left:0;}
+.table .span12{float:none;width:924px;margin-left:0;}
+.table .span13{float:none;width:1004px;margin-left:0;}
+.table .span14{float:none;width:1084px;margin-left:0;}
+.table .span15{float:none;width:1164px;margin-left:0;}
+.table .span16{float:none;width:1244px;margin-left:0;}
+.table .span17{float:none;width:1324px;margin-left:0;}
+.table .span18{float:none;width:1404px;margin-left:0;}
+.table .span19{float:none;width:1484px;margin-left:0;}
+.table .span20{float:none;width:1564px;margin-left:0;}
+.table .span21{float:none;width:1644px;margin-left:0;}
+.table .span22{float:none;width:1724px;margin-left:0;}
+.table .span23{float:none;width:1804px;margin-left:0;}
+.table .span24{float:none;width:1884px;margin-left:0;}
+.table tbody tr.success td{background-color:#dff0d8;}
+.table tbody tr.error td{background-color:#f2dede;}
+.table tbody tr.warning td{background-color:#fcf8e3;}
+.table tbody tr.info td{background-color:#d9edf7;}
+.table-hover tbody tr.success:hover td{background-color:#d0e9c6;}
+.table-hover tbody tr.error:hover td{background-color:#ebcccc;}
+.table-hover tbody tr.warning:hover td{background-color:#faf2cc;}
+.table-hover tbody tr.info:hover td{background-color:#c4e3f3;}
+form{margin:0 0 20px;}
+fieldset{padding:0;margin:0;border:0;}
+legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#999999;}
+label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;}
+input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
+label{display:block;margin-bottom:5px;}
+select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:9px;font-size:14px;line-height:20px;color:#555555;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+input,textarea,.uneditable-input{width:206px;}
+textarea{height:auto;}
+textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
+input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;cursor:pointer;}
+input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;}
+select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;}
+select{width:220px;border:1px solid #cccccc;background-color:#ffffff;}
+select[multiple],select[size]{height:auto;}
+select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.uneditable-input,.uneditable-textarea{color:#999999;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+.uneditable-input{overflow:hidden;white-space:nowrap;}
+.uneditable-textarea{width:auto;height:auto;}
+input:-moz-placeholder,textarea:-moz-placeholder{color:#999999;}
+input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999999;}
+input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999999;}
+.radio,.checkbox{min-height:18px;padding-left:18px;}
+.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
+.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
+.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
+.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
+.input-mini{width:60px;}
+.input-small{width:90px;}
+.input-medium{width:150px;}
+.input-large{width:210px;}
+.input-xlarge{width:270px;}
+.input-xxlarge{width:530px;}
+input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;}
+.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;}
+input,textarea,.uneditable-input{margin-left:0;}
+.controls-row [class*="span"]+[class*="span"]{margin-left:20px;}
+input.span12, textarea.span12, .uneditable-input.span12{width:926px;}
+input.span11, textarea.span11, .uneditable-input.span11{width:846px;}
+input.span10, textarea.span10, .uneditable-input.span10{width:766px;}
+input.span9, textarea.span9, .uneditable-input.span9{width:686px;}
+input.span8, textarea.span8, .uneditable-input.span8{width:606px;}
+input.span7, textarea.span7, .uneditable-input.span7{width:526px;}
+input.span6, textarea.span6, .uneditable-input.span6{width:446px;}
+input.span5, textarea.span5, .uneditable-input.span5{width:366px;}
+input.span4, textarea.span4, .uneditable-input.span4{width:286px;}
+input.span3, textarea.span3, .uneditable-input.span3{width:206px;}
+input.span2, textarea.span2, .uneditable-input.span2{width:126px;}
+input.span1, textarea.span1, .uneditable-input.span1{width:46px;}
+.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;}
+.controls-row:after{clear:both;}
+.controls-row [class*="span"]{float:left;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;}
+input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;}
+.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
+.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;}
+.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;}
+.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
+.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
+.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;}
+.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;}
+.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
+.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
+.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;}
+.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;}
+.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
+.control-group.info>label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad;}
+.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad;}
+.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;}
+.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad;}
+input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;}
+.form-actions:after{clear:both;}
+.help-block,.help-inline{color:#595959;}
+.help-block{display:block;margin-bottom:10px;}
+.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
+.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;font-size:14px;vertical-align:top;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;}
+.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#eeeeee;border:1px solid #ccc;}
+.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546;}
+.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
+.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append .add-on,.input-append .btn{margin-left:-1px;}
+.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
+.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
+.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
+.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
+.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;}
+.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
+.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;}
+.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
+.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
+.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;}
+.control-group{margin-bottom:10px;}
+legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;}
+.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;}
+.form-horizontal .control-group:after{clear:both;}
+.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;}
+.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;}
+.form-horizontal .help-block{margin-bottom:0;}
+.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px;}
+.form-horizontal .form-actions{padding-left:180px;}
+.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 14px;margin-bottom:0;font-size:14px;line-height:20px;*line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #bbbbbb;*border:0;border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;}
+.btn:active,.btn.active{background-color:#cccccc \9;}
+.btn:first-child{*margin-left:0;}
+.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
+.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn-large{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.btn-large [class^="icon-"]{margin-top:2px;}
+.btn-small{padding:3px 9px;font-size:12px;line-height:18px;}
+.btn-small [class^="icon-"]{margin-top:0;}
+.btn-mini{padding:2px 6px;font-size:11px;line-height:17px;}
+.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+.btn-block+.btn-block{margin-top:5px;}
+input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;}
+.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
+.btn{border-color:#c5c5c5;border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);}
+.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(to bottom, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#0044cc;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#0044cc;*background-color:#003bb3;}
+.btn-primary:active,.btn-primary.active{background-color:#003399 \9;}
+.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#f89406;*background-color:#df8505;}
+.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
+.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;}
+.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
+.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;}
+.btn-success:active,.btn-success.active{background-color:#408140 \9;}
+.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;}
+.btn-info:active,.btn-info.active{background-color:#24748c \9;}
+.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444444, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));background-image:-webkit-linear-gradient(top, #444444, #222222);background-image:-o-linear-gradient(top, #444444, #222222);background-image:linear-gradient(to bottom, #444444, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;}
+.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}
+button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
+button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
+button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
+button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
+.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn-link{border-color:transparent;cursor:pointer;color:#0088cc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent;}
+.btn-link[disabled]:hover{color:#333333;text-decoration:none;}
+[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px;}
+.icon-white,.nav-tabs>.active>a>[class^="icon-"],.nav-tabs>.active>a>[class*=" icon-"],.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png");}
+.icon-glass{background-position:0 0;}
+.icon-music{background-position:-24px 0;}
+.icon-search{background-position:-48px 0;}
+.icon-envelope{background-position:-72px 0;}
+.icon-heart{background-position:-96px 0;}
+.icon-star{background-position:-120px 0;}
+.icon-star-empty{background-position:-144px 0;}
+.icon-user{background-position:-168px 0;}
+.icon-film{background-position:-192px 0;}
+.icon-th-large{background-position:-216px 0;}
+.icon-th{background-position:-240px 0;}
+.icon-th-list{background-position:-264px 0;}
+.icon-ok{background-position:-288px 0;}
+.icon-remove{background-position:-312px 0;}
+.icon-zoom-in{background-position:-336px 0;}
+.icon-zoom-out{background-position:-360px 0;}
+.icon-off{background-position:-384px 0;}
+.icon-signal{background-position:-408px 0;}
+.icon-cog{background-position:-432px 0;}
+.icon-trash{background-position:-456px 0;}
+.icon-home{background-position:0 -24px;}
+.icon-file{background-position:-24px -24px;}
+.icon-time{background-position:-48px -24px;}
+.icon-road{background-position:-72px -24px;}
+.icon-download-alt{background-position:-96px -24px;}
+.icon-download{background-position:-120px -24px;}
+.icon-upload{background-position:-144px -24px;}
+.icon-inbox{background-position:-168px -24px;}
+.icon-play-circle{background-position:-192px -24px;}
+.icon-repeat{background-position:-216px -24px;}
+.icon-refresh{background-position:-240px -24px;}
+.icon-list-alt{background-position:-264px -24px;}
+.icon-lock{background-position:-287px -24px;}
+.icon-flag{background-position:-312px -24px;}
+.icon-headphones{background-position:-336px -24px;}
+.icon-volume-off{background-position:-360px -24px;}
+.icon-volume-down{background-position:-384px -24px;}
+.icon-volume-up{background-position:-408px -24px;}
+.icon-qrcode{background-position:-432px -24px;}
+.icon-barcode{background-position:-456px -24px;}
+.icon-tag{background-position:0 -48px;}
+.icon-tags{background-position:-25px -48px;}
+.icon-book{background-position:-48px -48px;}
+.icon-bookmark{background-position:-72px -48px;}
+.icon-print{background-position:-96px -48px;}
+.icon-camera{background-position:-120px -48px;}
+.icon-font{background-position:-144px -48px;}
+.icon-bold{background-position:-167px -48px;}
+.icon-italic{background-position:-192px -48px;}
+.icon-text-height{background-position:-216px -48px;}
+.icon-text-width{background-position:-240px -48px;}
+.icon-align-left{background-position:-264px -48px;}
+.icon-align-center{background-position:-288px -48px;}
+.icon-align-right{background-position:-312px -48px;}
+.icon-align-justify{background-position:-336px -48px;}
+.icon-list{background-position:-360px -48px;}
+.icon-indent-left{background-position:-384px -48px;}
+.icon-indent-right{background-position:-408px -48px;}
+.icon-facetime-video{background-position:-432px -48px;}
+.icon-picture{background-position:-456px -48px;}
+.icon-pencil{background-position:0 -72px;}
+.icon-map-marker{background-position:-24px -72px;}
+.icon-adjust{background-position:-48px -72px;}
+.icon-tint{background-position:-72px -72px;}
+.icon-edit{background-position:-96px -72px;}
+.icon-share{background-position:-120px -72px;}
+.icon-check{background-position:-144px -72px;}
+.icon-move{background-position:-168px -72px;}
+.icon-step-backward{background-position:-192px -72px;}
+.icon-fast-backward{background-position:-216px -72px;}
+.icon-backward{background-position:-240px -72px;}
+.icon-play{background-position:-264px -72px;}
+.icon-pause{background-position:-288px -72px;}
+.icon-stop{background-position:-312px -72px;}
+.icon-forward{background-position:-336px -72px;}
+.icon-fast-forward{background-position:-360px -72px;}
+.icon-step-forward{background-position:-384px -72px;}
+.icon-eject{background-position:-408px -72px;}
+.icon-chevron-left{background-position:-432px -72px;}
+.icon-chevron-right{background-position:-456px -72px;}
+.icon-plus-sign{background-position:0 -96px;}
+.icon-minus-sign{background-position:-24px -96px;}
+.icon-remove-sign{background-position:-48px -96px;}
+.icon-ok-sign{background-position:-72px -96px;}
+.icon-question-sign{background-position:-96px -96px;}
+.icon-info-sign{background-position:-120px -96px;}
+.icon-screenshot{background-position:-144px -96px;}
+.icon-remove-circle{background-position:-168px -96px;}
+.icon-ok-circle{background-position:-192px -96px;}
+.icon-ban-circle{background-position:-216px -96px;}
+.icon-arrow-left{background-position:-240px -96px;}
+.icon-arrow-right{background-position:-264px -96px;}
+.icon-arrow-up{background-position:-289px -96px;}
+.icon-arrow-down{background-position:-312px -96px;}
+.icon-share-alt{background-position:-336px -96px;}
+.icon-resize-full{background-position:-360px -96px;}
+.icon-resize-small{background-position:-384px -96px;}
+.icon-plus{background-position:-408px -96px;}
+.icon-minus{background-position:-433px -96px;}
+.icon-asterisk{background-position:-456px -96px;}
+.icon-exclamation-sign{background-position:0 -120px;}
+.icon-gift{background-position:-24px -120px;}
+.icon-leaf{background-position:-48px -120px;}
+.icon-fire{background-position:-72px -120px;}
+.icon-eye-open{background-position:-96px -120px;}
+.icon-eye-close{background-position:-120px -120px;}
+.icon-warning-sign{background-position:-144px -120px;}
+.icon-plane{background-position:-168px -120px;}
+.icon-calendar{background-position:-192px -120px;}
+.icon-random{background-position:-216px -120px;width:16px;}
+.icon-comment{background-position:-240px -120px;}
+.icon-magnet{background-position:-264px -120px;}
+.icon-chevron-up{background-position:-288px -120px;}
+.icon-chevron-down{background-position:-313px -119px;}
+.icon-retweet{background-position:-336px -120px;}
+.icon-shopping-cart{background-position:-360px -120px;}
+.icon-folder-close{background-position:-384px -120px;}
+.icon-folder-open{background-position:-408px -120px;width:16px;}
+.icon-resize-vertical{background-position:-432px -119px;}
+.icon-resize-horizontal{background-position:-456px -118px;}
+.icon-hdd{background-position:0 -144px;}
+.icon-bullhorn{background-position:-24px -144px;}
+.icon-bell{background-position:-48px -144px;}
+.icon-certificate{background-position:-72px -144px;}
+.icon-thumbs-up{background-position:-96px -144px;}
+.icon-thumbs-down{background-position:-120px -144px;}
+.icon-hand-right{background-position:-144px -144px;}
+.icon-hand-left{background-position:-168px -144px;}
+.icon-hand-up{background-position:-192px -144px;}
+.icon-hand-down{background-position:-216px -144px;}
+.icon-circle-arrow-right{background-position:-240px -144px;}
+.icon-circle-arrow-left{background-position:-264px -144px;}
+.icon-circle-arrow-up{background-position:-288px -144px;}
+.icon-circle-arrow-down{background-position:-312px -144px;}
+.icon-globe{background-position:-336px -144px;}
+.icon-wrench{background-position:-360px -144px;}
+.icon-tasks{background-position:-384px -144px;}
+.icon-filter{background-position:-408px -144px;}
+.icon-briefcase{background-position:-432px -144px;}
+.icon-fullscreen{background-position:-456px -144px;}
+.btn-group{position:relative;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;}
+.btn-group+.btn-group{margin-left:5px;}
+.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
+.btn-toolbar .btn+.btn,.btn-toolbar .btn-group+.btn,.btn-toolbar .btn+.btn-group{margin-left:5px;}
+.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-group>.btn+.btn{margin-left:-1px;}
+.btn-group>.btn,.btn-group>.dropdown-menu{font-size:14px;}
+.btn-group>.btn-mini{font-size:11px;}
+.btn-group>.btn-small{font-size:12px;}
+.btn-group>.btn-large{font-size:16px;}
+.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
+.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
+.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
+.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;}
+.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
+.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:5px;*padding-bottom:5px;}
+.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;}
+.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;}
+.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;}
+.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;}
+.btn-group.open .btn-primary.dropdown-toggle{background-color:#0044cc;}
+.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;}
+.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;}
+.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;}
+.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;}
+.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;}
+.btn .caret{margin-top:8px;margin-left:0;}
+.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px;}
+.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;}
+.dropup .btn-large .caret{border-bottom:5px solid #000000;border-top:0;}
+.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;}
+.btn-group-vertical .btn{display:block;float:none;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-group-vertical .btn+.btn{margin-left:0;margin-top:-1px;}
+.btn-group-vertical .btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
+.btn-group-vertical .btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
+.btn-group-vertical .btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;}
+.btn-group-vertical .btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
+.nav{margin-left:0;margin-bottom:20px;list-style:none;}
+.nav>li>a{display:block;}
+.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
+.nav>.pull-right{float:right;}
+.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
+.nav li+.nav-header{margin-top:9px;}
+.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
+.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.nav-list>li>a{padding:3px 15px;}
+.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
+.nav-list [class^="icon-"]{margin-right:2px;}
+.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
+.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;}
+.nav-tabs:after,.nav-pills:after{clear:both;}
+.nav-tabs>li,.nav-pills>li{float:left;}
+.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
+.nav-tabs{border-bottom:1px solid #ddd;}
+.nav-tabs>li{margin-bottom:-1px;}
+.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
+.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;}
+.nav-stacked>li{float:none;}
+.nav-stacked>li>a{margin-right:0;}
+.nav-tabs.nav-stacked{border-bottom:0;}
+.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
+.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
+.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
+.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
+.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
+.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.nav .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;}
+.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;}
+.nav-tabs .dropdown-toggle .caret{margin-top:8px;}
+.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;}
+.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;}
+.nav>.dropdown.active>a:hover{cursor:pointer;}
+.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
+.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
+.tabs-stacked .open>a:hover{border-color:#999999;}
+.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;}
+.tabbable:after{clear:both;}
+.tab-content{overflow:auto;}
+.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.tabs-below>.nav-tabs{border-top:1px solid #ddd;}
+.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;}
+.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
+.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;}
+.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;}
+.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
+.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
+.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
+.tabs-left>.nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
+.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
+.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
+.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.tabs-right>.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
+.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
+.nav>.disabled>a{color:#999999;}
+.nav>.disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default;}
+.navbar{overflow:visible;margin-bottom:20px;color:#777777;*position:relative;*z-index:2;}
+.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #ffffff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #ffffff, #f2f2f2);background-image:-o-linear-gradient(top, #ffffff, #f2f2f2);background-image:linear-gradient(to bottom, #ffffff, #f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;}
+.navbar-inner:after{clear:both;}
+.navbar .container{width:auto;}
+.nav-collapse.collapse{height:auto;}
+.navbar .brand{float:left;display:block;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777777;text-shadow:0 1px 0 #ffffff;}.navbar .brand:hover{text-decoration:none;}
+.navbar-text{margin-bottom:0;line-height:40px;}
+.navbar-link{color:#777777;}.navbar-link:hover{color:#333333;}
+.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #ffffff;}
+.navbar .btn,.navbar .btn-group{margin-top:5px;}
+.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0;}
+.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;}
+.navbar-form:after{clear:both;}
+.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
+.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;}
+.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
+.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
+.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.navbar-static-top{position:static;width:100%;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
+.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;}
+.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;}
+.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.navbar-fixed-top{top:0;}
+.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);}
+.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);}
+.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
+.navbar .nav.pull-right{float:right;margin-right:0;}
+.navbar .nav>li{float:left;}
+.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777777;text-decoration:none;text-shadow:0 1px 0 #ffffff;}
+.navbar .nav .dropdown-toggle .caret{margin-top:8px;}
+.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333333;text-decoration:none;}
+.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);}
+.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ededed;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#e5e5e5;*background-color:#d9d9d9;}
+.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#cccccc \9;}
+.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
+.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
+.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
+.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
+.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
+.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
+.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555555;}
+.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777777;border-bottom-color:#777777;}
+.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;}
+.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;}
+.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;}
+.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
+.navbar-inverse{color:#999999;}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222222, #111111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));background-image:-webkit-linear-gradient(top, #222222, #111111);background-image:-o-linear-gradient(top, #222222, #111111);background-image:linear-gradient(to bottom, #222222, #111111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525;}
+.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999999;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#ffffff;}
+.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#ffffff;}
+.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:#111111;}
+.navbar-inverse .navbar-link{color:#999999;}.navbar-inverse .navbar-link:hover{color:#ffffff;}
+.navbar-inverse .divider-vertical{border-left-color:#111111;border-right-color:#222222;}
+.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111111;color:#ffffff;}
+.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999999;border-bottom-color:#999999;}
+.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#515151;border-color:#111111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#cccccc;}
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#cccccc;}
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
+.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
+.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#040404;*background-color:#000000;}
+.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000000 \9;}
+.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}
+.breadcrumb .divider{padding:0 5px;color:#ccc;}
+.breadcrumb .active{color:#999999;}
+.pagination{height:40px;margin:20px 0;}
+.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination ul>li{display:inline;}
+.pagination ul>li>a,.pagination ul>li>span{float:left;padding:0 14px;line-height:38px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;}
+.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;}
+.pagination ul>.active>a,.pagination ul>.active>span{color:#999999;cursor:default;}
+.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999999;background-color:transparent;cursor:default;}
+.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.pagination-centered{text-align:center;}
+.pagination-right{text-align:right;}
+.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;}
+.pager:after{clear:both;}
+.pager li{display:inline;}
+.pager a,.pager span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.pager a:hover{text-decoration:none;background-color:#f5f5f5;}
+.pager .next a,.pager .next span{float:right;}
+.pager .previous a{float:left;}
+.pager .disabled a,.pager .disabled a:hover,.pager .disabled span{color:#999999;background-color:#fff;cursor:default;}
+.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;}
+.thumbnails:after{clear:both;}
+.row-fluid .thumbnails{margin-left:0;}
+.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;}
+.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;}
+a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
+.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
+.thumbnail .caption{padding:9px;color:#555555;}
+.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;}
+.alert h4{margin:0;}
+.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;}
+.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
+.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
+.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
+.alert-block{padding-top:14px;padding-bottom:14px;}
+.alert-block>p,.alert-block>ul{margin-bottom:0;}
+.alert-block p+p{margin-top:5px;}
+@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
+.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);}
+.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
+.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
+.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);}
+.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);}
+.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);}
+.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);}
+.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
+.hero-unit p{font-size:18px;font-weight:200;line-height:30px;color:inherit;}
+.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
+.tooltip.top{margin-top:-3px;}
+.tooltip.right{margin-left:3px;}
+.tooltip.bottom{margin-top:3px;}
+.tooltip.left{margin-left:-3px;}
+.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;}
+.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;}
+.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;}
+.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;}
+.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;}
+.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);}.popover.top{margin-bottom:10px;}
+.popover.right{margin-left:10px;}
+.popover.bottom{margin-top:10px;}
+.popover.left{margin-right:10px;}
+.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;}
+.popover-content{padding:9px 14px;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
+.popover .arrow,.popover .arrow:after{position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid;}
+.popover .arrow:after{content:"";z-index:-1;}
+.popover.top .arrow{bottom:-10px;left:50%;margin-left:-10px;border-width:10px 10px 0;border-top-color:#ffffff;}.popover.top .arrow:after{border-width:11px 11px 0;border-top-color:rgba(0, 0, 0, 0.25);bottom:-1px;left:-11px;}
+.popover.right .arrow{top:50%;left:-10px;margin-top:-10px;border-width:10px 10px 10px 0;border-right-color:#ffffff;}.popover.right .arrow:after{border-width:11px 11px 11px 0;border-right-color:rgba(0, 0, 0, 0.25);bottom:-11px;left:-1px;}
+.popover.bottom .arrow{top:-10px;left:50%;margin-left:-10px;border-width:0 10px 10px;border-bottom-color:#ffffff;}.popover.bottom .arrow:after{border-width:0 11px 11px;border-bottom-color:rgba(0, 0, 0, 0.25);top:-1px;left:-11px;}
+.popover.left .arrow{top:50%;right:-10px;margin-top:-10px;border-width:10px 0 10px 10px;border-left-color:#ffffff;}.popover.left .arrow:after{border-width:11px 0 11px 11px;border-left-color:rgba(0, 0, 0, 0.25);bottom:-11px;right:-1px;}
+.modal-open .modal .dropdown-menu{z-index:2050;}
+.modal-open .modal .dropdown.open{*z-index:2050;}
+.modal-open .modal .popover{z-index:2060;}
+.modal-open .modal .tooltip{z-index:2080;}
+.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
+.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:50%;}
+.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
+.modal-header h3{margin:0;line-height:30px;}
+.modal-body{overflow-y:auto;max-height:400px;padding:15px;}
+.modal-form{margin-bottom:0;}
+.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
+.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
+.dropup,.dropdown{position:relative;}
+.dropdown-toggle{*margin-bottom:-3px;}
+.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
+.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";}
+.dropdown .caret{margin-top:8px;margin-left:2px;}
+.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;}
+.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
+.dropdown-menu a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;}
+.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{text-decoration:none;color:#ffffff;background-color:#0088cc;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);}
+.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;outline:0;background-color:#0088cc;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);}
+.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999999;}
+.dropdown-menu .disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default;}
+.open{*z-index:1000;}.open >.dropdown-menu{display:block;}
+.pull-right>.dropdown-menu{right:0;left:auto;}
+.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";}
+.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
+.dropdown-submenu{position:relative;}
+.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.dropdown-submenu:hover>.dropdown-menu{display:block;}
+.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
+.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
+.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;}
+.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.accordion{margin-bottom:20px;}
+.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.accordion-heading{border-bottom:0;}
+.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
+.accordion-toggle{cursor:pointer;}
+.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
+.carousel{position:relative;margin-bottom:20px;line-height:1;}
+.carousel-inner{overflow:hidden;width:100%;position:relative;}
+.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
+.carousel .item>img{display:block;line-height:1;}
+.carousel .active,.carousel .next,.carousel .prev{display:block;}
+.carousel .active{left:0;}
+.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;}
+.carousel .next{left:100%;}
+.carousel .prev{left:-100%;}
+.carousel .next.left,.carousel .prev.right{left:0;}
+.carousel .active.left{left:-100%;}
+.carousel .active.right{left:100%;}
+.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
+.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
+.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);}
+.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;}
+.carousel-caption h4{margin:0 0 5px;}
+.carousel-caption p{margin-bottom:0;}
+.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);}
+button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.invisible{visibility:hidden;}
+.affix{position:fixed;}
+.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;}
+.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;}
+.hidden{display:none;visibility:hidden;}
+.visible-phone{display:none !important;}
+.visible-tablet{display:none !important;}
+.hidden-desktop{display:none !important;}
+.visible-desktop{display:inherit !important;}
+@media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important ;} .visible-tablet{display:inherit !important;} .hidden-tablet{display:none !important;}}@media (max-width:767px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important;} .visible-phone{display:inherit !important;} .hidden-phone{display:none !important;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px;} .container-fluid{padding:0;} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left;} .dl-horizontal dd{margin-left:0;} .container{width:auto;} .row-fluid{width:100%;} .row,.thumbnails{margin-left:0;} .thumbnails>li{float:none;margin-left:0;} [class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto;} .controls-row [class*="span"]+[class*="span"]{margin-left:0;} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0;}.modal.fade.in{top:auto;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:20px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .modal{top:10px;left:10px;right:10px;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:20px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%;} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%;} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%;} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%;} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%;} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%;} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%;} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%;} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%;} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%;} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%;} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%;} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%;} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%;} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%;} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%;} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%;} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%;} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%;} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%;} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%;} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%;} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%;} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%;} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%;} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%;} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%;} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%;} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%;} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%;} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%;} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%;} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%;} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%;} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:20px;} input.span12, textarea.span12, .uneditable-input.span12{width:710px;} input.span11, textarea.span11, .uneditable-input.span11{width:648px;} input.span10, textarea.span10, .uneditable-input.span10{width:586px;} input.span9, textarea.span9, .uneditable-input.span9{width:524px;} input.span8, textarea.span8, .uneditable-input.span8{width:462px;} input.span7, textarea.span7, .uneditable-input.span7{width:400px;} input.span6, textarea.span6, .uneditable-input.span6{width:338px;} input.span5, textarea.span5, .uneditable-input.span5{width:276px;} input.span4, textarea.span4, .uneditable-input.span4{width:214px;} input.span3, textarea.span3, .uneditable-input.span3{width:152px;} input.span2, textarea.span2, .uneditable-input.span2{width:90px;} input.span1, textarea.span1, .uneditable-input.span1{width:28px;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:30px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%;} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%;} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%;} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%;} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%;} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%;} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%;} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%;} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%;} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%;} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%;} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%;} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%;} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%;} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%;} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%;} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%;} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%;} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%;} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%;} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%;} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%;} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%;} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%;} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%;} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%;} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%;} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%;} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%;} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%;} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%;} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%;} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%;} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%;} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:30px;} input.span12, textarea.span12, .uneditable-input.span12{width:1156px;} input.span11, textarea.span11, .uneditable-input.span11{width:1056px;} input.span10, textarea.span10, .uneditable-input.span10{width:956px;} input.span9, textarea.span9, .uneditable-input.span9{width:856px;} input.span8, textarea.span8, .uneditable-input.span8{width:756px;} input.span7, textarea.span7, .uneditable-input.span7{width:656px;} input.span6, textarea.span6, .uneditable-input.span6{width:556px;} input.span5, textarea.span5, .uneditable-input.span5{width:456px;} input.span4, textarea.span4, .uneditable-input.span4{width:356px;} input.span3, textarea.span3, .uneditable-input.span3{width:256px;} input.span2, textarea.span2, .uneditable-input.span2{width:156px;} input.span1, textarea.span1, .uneditable-input.span1{width:56px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;} .row-fluid .thumbnails{margin-left:0;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top,.navbar-fixed-bottom{position:static;} .navbar-fixed-top{margin-bottom:20px;} .navbar-fixed-bottom{margin-top:20px;} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .nav-collapse{clear:both;} .nav-collapse .nav{float:none;margin:0 0 10px;} .nav-collapse .nav>li{float:none;} .nav-collapse .nav>li>a{margin-bottom:2px;} .nav-collapse .nav>.divider-vertical{display:none;} .nav-collapse .nav .nav-header{color:#777777;text-shadow:none;} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px;} .nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2;} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111111;} .nav-collapse.in .btn-group{margin-top:5px;padding:0;} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:block;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none;} .nav-collapse .dropdown-menu .divider{display:none;} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none;} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111111;border-bottom-color:#111111;} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0;} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0;} .navbar .btn-navbar{display:block;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}}
diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css
new file mode 100644
index 00000000..e29da395
--- /dev/null
+++ b/rest_framework/static/rest_framework/css/default.css
@@ -0,0 +1,170 @@
+
+/* The navbar is fixed at >= 980px wide, so add padding to the body to prevent
+content running up underneath it. */
+
+h1 {
+ font-weight: 500;
+}
+
+h2, h3 {
+ font-weight: 300;
+}
+
+.resource-description, .response-info {
+ margin-bottom: 2em;
+}
+.version:before {
+ content: "v";
+ opacity: 0.6;
+ padding-right: 0.25em;
+}
+
+.version {
+ font-size: 70%;
+}
+
+.format-option {
+ font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;
+}
+
+.button-form {
+ float: right;
+ margin-right: 1em;
+}
+
+ul.breadcrumb {
+ margin: 58px 0 0 0;
+}
+
+/* To allow tooltips to work on disabled elements */
+.disabled-tooltip-shield {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.errorlist {
+ margin-top: 0.5em;
+}
+
+pre {
+ overflow: auto;
+ word-wrap: normal;
+ white-space: pre;
+ font-size: 12px;
+}
+
+.page-header {
+ border-bottom: none;
+ padding-bottom: 0px;
+ margin-bottom: 20px;
+}
+
+
+/*=== dabapps bootstrap styles ====*/
+
+html{
+ width:100%;
+ background: none;
+}
+
+body, .navbar .navbar-inner .container-fluid {
+ max-width: 1150px;
+ margin: 0 auto;
+}
+
+body{
+ background: url("../img/grid.png") repeat-x;
+ background-attachment: fixed;
+}
+
+#content{
+ margin: 0;
+}
+/* custom navigation styles */
+.wrapper .navbar{
+ width: 100%;
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.navbar .navbar-inner{
+ background: #2C2C2C;
+ color: white;
+ border: none;
+ border-top: 5px solid #A30000;
+ border-radius: 0px;
+}
+
+.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand{
+ color: white;
+}
+
+.nav-list > .active > a, .nav-list > .active > a:hover {
+ background: #2c2c2c;
+}
+
+.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li{
+ color: #A30000;
+}
+.navbar .navbar-inner .dropdown-menu li a:hover{
+ background: #eeeeee;
+ color: #c20000;
+}
+
+/* custom general page styles */
+.hero-unit h2, .hero-unit h1{
+ color: #A30000;
+}
+
+body a, body a{
+ color: #A30000;
+}
+
+body a:hover{
+ color: #c20000;
+}
+
+#content a span{
+ text-decoration: underline;
+ }
+
+/* sticky footer and footer */
+html, body {
+ height: 100%;
+}
+.wrapper {
+ min-height: 100%;
+ height: auto !important;
+ height: 100%;
+ margin: 0 auto -60px;
+}
+
+
+#footer, #push {
+ height: 60px; /* .push must be the same height as .footer */
+}
+
+#footer{
+ text-align: right;
+}
+
+#footer p {
+ text-align: center;
+ color: gray;
+ border-top: 1px solid #DDD;
+ padding-top: 10px;
+}
+
+#footer a {
+ color: gray;
+ font-weight: bold;
+}
+
+#footer a:hover {
+ color: gray;
+}
+
diff --git a/rest_framework/static/rest_framework/css/prettify.css b/rest_framework/static/rest_framework/css/prettify.css
new file mode 100644
index 00000000..d437aff6
--- /dev/null
+++ b/rest_framework/static/rest_framework/css/prettify.css
@@ -0,0 +1,30 @@
+.com { color: #93a1a1; }
+.lit { color: #195f91; }
+.pun, .opn, .clo { color: #93a1a1; }
+.fun { color: #dc322f; }
+.str, .atv { color: #D14; }
+.kwd, .prettyprint .tag { color: #1e347b; }
+.typ, .atn, .dec, .var { color: teal; }
+.pln { color: #48484c; }
+
+.prettyprint {
+ padding: 8px;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+.prettyprint.linenums {
+ -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
+ -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
+ box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
+}
+
+/* Specify class=linenums on a pre to get line numbering */
+ol.linenums {
+ margin: 0 0 0 33px; /* IE indents via margin-left */
+}
+ol.linenums li {
+ padding-left: 12px;
+ color: #bebec5;
+ line-height: 20px;
+ text-shadow: 0 1px 0 #fff;
+} \ No newline at end of file
diff --git a/rest_framework/static/rest_framework/img/glyphicons-halflings-white.png b/rest_framework/static/rest_framework/img/glyphicons-halflings-white.png
new file mode 100644
index 00000000..3bf6484a
--- /dev/null
+++ b/rest_framework/static/rest_framework/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/rest_framework/static/rest_framework/img/glyphicons-halflings.png b/rest_framework/static/rest_framework/img/glyphicons-halflings.png
new file mode 100644
index 00000000..36c3b1ed
--- /dev/null
+++ b/rest_framework/static/rest_framework/img/glyphicons-halflings.png
Binary files differ
diff --git a/rest_framework/static/rest_framework/img/grid.png b/rest_framework/static/rest_framework/img/grid.png
new file mode 100644
index 00000000..878c3ed5
--- /dev/null
+++ b/rest_framework/static/rest_framework/img/grid.png
Binary files differ
diff --git a/rest_framework/static/rest_framework/js/bootstrap.min.js b/rest_framework/static/rest_framework/js/bootstrap.min.js
new file mode 100644
index 00000000..e0b220f4
--- /dev/null
+++ b/rest_framework/static/rest_framework/js/bootstrap.min.js
@@ -0,0 +1,7 @@
+/**
+* Bootstrap.js by @fat & @mdo
+* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-affix.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;a("body").addClass("modal-open"),this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1).focus(),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,a("body").removeClass("modal-open"),this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(a){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(this.removeBackdrop,this)):this.removeBackdrop()):b&&b()}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f).one("hide",function(){c.focus()})})})}(window.jQuery),!function(a){function d(){e(a(b)).removeClass("open")}function e(b){var c=b.attr("data-target"),d;return c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,"")),d=a(c),d.length||(d=b.parent()),d}var b="[data-toggle=dropdown]",c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),f,g;if(c.is(".disabled, :disabled"))return;return f=e(c),g=f.hasClass("open"),d(),g||(f.toggleClass("open"),c.focus()),!1},keydown:function(b){var c,d,f,g,h,i;if(!/(38|40|27)/.test(b.keyCode))return;c=a(this),b.preventDefault(),b.stopPropagation();if(c.is(".disabled, :disabled"))return;g=e(c),h=g.hasClass("open");if(!h||h&&b.keyCode==27)return c.click();d=a("[role=menu] li:not(.divider) a",g);if(!d.length)return;i=d.index(d.filter(":focus")),b.keyCode==38&&i>0&&i--,b.keyCode==40&&i<d.length-1&&i++,~i||(i=0),d.eq(i).focus()}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api touchstart.dropdown.data-api",d),a("body").on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",b,c.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",b+", [role=menu]",c.prototype.keydown)})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll-spy.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var b=a(this),c=b.data("target")||b.attr("href"),d=/^#\w/.test(c)&&a(c);return d&&d.length&&[[d.position().top,c]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu").length&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this)):this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f+"."+this.type,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();return c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove(),this},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(a){var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content > *")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(a){var b=function(b,c){this.options=a.extend({},a.fn.affix.defaults,c),this.$window=a(window).on("scroll.affix.data-api",a.proxy(this.checkPosition,this)),this.$element=a(b),this.checkPosition()};b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var b=a(document).height(),c=this.$window.scrollTop(),d=this.$element.offset(),e=this.options.offset,f=e.bottom,g=e.top,h="affix affix-top affix-bottom",i;typeof e!="object"&&(f=g=e),typeof g=="function"&&(g=e.top()),typeof f=="function"&&(f=e.bottom()),i=this.unpin!=null&&c+this.unpin<=d.top?!1:f!=null&&d.top+this.$element.height()>=b-f?"bottom":g!=null&&c<=g?"top":!1;if(this.affixed===i)return;this.affixed=i,this.unpin=i=="bottom"?d.top-c:null,this.$element.removeClass(h).addClass("affix"+(i?"-"+i:""))},a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("affix"),f=typeof c=="object"&&c;e||d.data("affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.defaults={offset:0},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery),!function(a){var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),a.support.transition&&this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();c[a(e).hasClass("in")?"addClass":"removeClass"]("collapsed"),a(e).collapse(f)})})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".item.active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide",{relatedTarget:e[0]});this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(c=a.isFunction(this.source)?this.source(this.query,a.proxy(this.process,this)):this.source,c?this.process(c):this)},process:function(b){var c=this;return b=a.grep(b,function(a){return c.matcher(a)}),b=this.sorter(b),b.length?this.render(b.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.chrome||a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keydown,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},move:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},keydown:function(b){this.suppressKeyPressRepeat=!~a.inArray(b.keyCode,[40,38,9,13,27]),this.move(b)},keypress:function(a){if(this.suppressKeyPressRepeat)return;this.move(a)},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery) \ No newline at end of file
diff --git a/rest_framework/static/rest_framework/js/default.js b/rest_framework/static/rest_framework/js/default.js
new file mode 100644
index 00000000..ecaccc0f
--- /dev/null
+++ b/rest_framework/static/rest_framework/js/default.js
@@ -0,0 +1,5 @@
+prettyPrint();
+
+$('.js-tooltip').tooltip({
+ delay: 1000
+});
diff --git a/rest_framework/static/rest_framework/js/jquery-1.8.1-min.js b/rest_framework/static/rest_framework/js/jquery-1.8.1-min.js
new file mode 100644
index 00000000..3b8d15d0
--- /dev/null
+++ b/rest_framework/static/rest_framework/js/jquery-1.8.1-min.js
@@ -0,0 +1,2 @@
+/*! jQuery v@1.8.1 jquery.com | jquery.org/license */
+(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.1",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":a.toString().replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||f.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return typeof a=="object"?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length||!d)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||++p.uuid:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")===0&&(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)~f.indexOf(" "+b[g]+" ")||(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>-1)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,""+d),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,k,l,m,n=(p._data(this,"events")||{})[c.type]||[],o=n.delegateCount,q=[].slice.call(arguments),r=!c.exclusive&&!c.namespace,s=p.event.special[c.type]||{},t=[];q[0]=c,c.delegateTarget=this;if(s.preDispatch&&s.preDispatch.call(this,c)===!1)return;if(o&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<o;d++)k=n[d],l=k.selector,h[l]===b&&(h[l]=p(l,this).index(f)>=0),h[l]&&j.push(k);j.length&&t.push({elem:f,matches:j})}n.length>o&&t.push({elem:this,matches:n.slice(o)});for(d=0;d<t.length&&!c.isPropagationStopped();d++){i=t[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){k=i.matches[e];if(r||!c.namespace&&!k.namespace||c.namespace_re&&c.namespace_re.test(k.namespace))c.data=k.data,c.handleObj=k,g=((p.event.special[k.origType]||{}).handle||k.handler).apply(i.elem,q),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return s.postDispatch&&s.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function $(a,b,c,d){c=c||[],b=b||q;var e,f,g,j,k=b.nodeType;if(k!==1&&k!==9)return[];if(!a||typeof a!="string")return c;g=h(b);if(!g&&!d)if(e=L.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&i(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return u.apply(c,t.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&X&&b.getElementsByClassName)return u.apply(c,t.call(b.getElementsByClassName(j),0)),c}return bk(a,b,c,d,g)}function _(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function ba(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bb(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bc(a,b,c,d){var e,g,h,i,j,k,l,m,n,p,r=!c&&b!==q,s=(r?"<s>":"")+a.replace(H,"$1<s>"),u=y[o][s];if(u)return d?0:t.call(u,0);j=a,k=[],m=0,n=f.preFilter,p=f.filter;while(j){if(!e||(g=I.exec(j)))g&&(j=j.slice(g[0].length),h.selector=l),k.push(h=[]),l="",r&&(j=" "+j);e=!1;if(g=J.exec(j))l+=g[0],j=j.slice(g[0].length),e=h.push({part:g.pop().replace(H," "),string:g[0],captures:g});for(i in p)(g=S[i].exec(j))&&(!n[i]||(g=n[i](g,b,c)))&&(l+=g[0],j=j.slice(g[0].length),e=h.push({part:i,string:g.shift(),captures:g}));if(!e)break}return l&&(h.selector=l),d?j.length:j?$.error(a):t.call(y(s,k),0)}function bd(a,b,e,f){var g=b.dir,h=s++;return a||(a=function(a){return a===e}),b.first?function(b){while(b=b[g])if(b.nodeType===1)return a(b)&&b}:f?function(b){while(b=b[g])if(b.nodeType===1&&a(b))return b}:function(b){var e,f=h+"."+c,i=f+"."+d;while(b=b[g])if(b.nodeType===1){if((e=b[o])===i)return b.sizset;if(typeof e=="string"&&e.indexOf(f)===0){if(b.sizset)return b}else{b[o]=i;if(a(b))return b.sizset=!0,b;b.sizset=!1}}}}function be(a,b){return a?function(c){var d=b(c);return d&&a(d===!0?c:d)}:b}function bf(a,b,c){var d,e,g=0;for(;d=a[g];g++)f.relative[d.part]?e=bd(e,f.relative[d.part],b,c):e=be(e,f.filter[d.part].apply(null,d.captures.concat(b,c)));return e}function bg(a){return function(b){var c,d=0;for(;c=a[d];d++)if(c(b))return!0;return!1}}function bh(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)$(a,b[e],c,d)}function bi(a,b,c,d,e,g){var h,i=f.setFilters[b.toLowerCase()];return i||$.error(b),(a||!(h=e))&&bh(a||"*",d,h=[],e),h.length>0?i(h,c,g):[]}function bj(a,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s=0,t=a.length,v=S.POS,w=new RegExp("^"+v.source+"(?!"+A+")","i"),x=function(){var a=1,c=arguments.length-2;for(;a<c;a++)arguments[a]===b&&(n[a]=b)};for(;s<t;s++){f=a[s],g="",m=e;for(h=0,i=f.length;h<i;h++){j=f[h],k=j.string;if(j.part==="PSEUDO"){v.exec(""),l=0;while(n=v.exec(k)){o=!0,p=v.lastIndex=n.index+n[0].length;if(p>l){g+=k.slice(l,n.index),l=p,q=[c],J.test(g)&&(m&&(q=m),m=e);if(r=O.test(g))g=g.slice(0,-5).replace(J,"$&*"),l++;n.length>1&&n[0].replace(w,x),m=bi(g,n[1],n[2],q,m,r)}g=""}}o||(g+=k),o=!1}g?J.test(g)?bh(g,m||[c],d,e):$(g,c,d,e?e.concat(m):m):u.apply(d,m)}return t===1?d:$.uniqueSort(d)}function bk(a,b,e,g,h){a=a.replace(H,"$1");var i,k,l,m,n,o,p,q,r,s,v=bc(a,b,h),w=b.nodeType;if(S.POS.test(a))return bj(v,b,e,g);if(g)i=t.call(g,0);else if(v.length===1){if((o=t.call(v[0],0)).length>2&&(p=o[0]).part==="ID"&&w===9&&!h&&f.relative[o[1].part]){b=f.find.ID(p.captures[0].replace(R,""),b,h)[0];if(!b)return e;a=a.slice(o.shift().string.length)}r=(v=N.exec(o[0].string))&&!v.index&&b.parentNode||b,q="";for(n=o.length-1;n>=0;n--){p=o[n],s=p.part,q=p.string+q;if(f.relative[s])break;if(f.order.test(s)){i=f.find[s](p.captures[0].replace(R,""),r,h);if(i==null)continue;a=a.slice(0,a.length-q.length)+q.replace(S[s],""),a||u.apply(e,t.call(i,0));break}}}if(a){k=j(a,b,h),c=k.dirruns++,i==null&&(i=f.find.TAG("*",N.test(a)&&b.parentNode||b));for(n=0;m=i[n];n++)d=k.runs++,k(m)&&e.push(m)}return e}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=a.document,r=q.documentElement,s=0,t=[].slice,u=[].push,v=function(a,b){return a[o]=b||!0,a},w=function(){var a={},b=[];return v(function(c,d){return b.push(c)>f.cacheLength&&delete a[b.shift()],a[c]=d},a)},x=w(),y=w(),z=w(),A="[\\x20\\t\\r\\n\\f]",B="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",C=B.replace("w","w#"),D="([*^$|!~]?=)",E="\\["+A+"*("+B+")"+A+"*(?:"+D+A+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+C+")|)|)"+A+"*\\]",F=":("+B+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+E+")|[^:]|\\\\.)*|.*))\\)|)",G=":(nth|eq|gt|lt|first|last|even|odd)(?:\\(((?:-\\d)?\\d*)\\)|)(?=[^-]|$)",H=new RegExp("^"+A+"+|((?:^|[^\\\\])(?:\\\\.)*)"+A+"+$","g"),I=new RegExp("^"+A+"*,"+A+"*"),J=new RegExp("^"+A+"*([\\x20\\t\\r\\n\\f>+~])"+A+"*"),K=new RegExp(F),L=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,M=/^:not/,N=/[\x20\t\r\n\f]*[+~]/,O=/:not\($/,P=/h\d/i,Q=/input|select|textarea|button/i,R=/\\(?!\\)/g,S={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),NAME:new RegExp("^\\[name=['\"]?("+B+")['\"]?\\]"),TAG:new RegExp("^("+B.replace("w","w*")+")"),ATTR:new RegExp("^"+E),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|nth|last|first)-child(?:\\("+A+"*(even|odd|(([+-]|)(\\d*)n|)"+A+"*(?:([+-]|)"+A+"*(\\d+)|))"+A+"*\\)|)","i"),POS:new RegExp(G,"ig"),needsContext:new RegExp("^"+A+"*[>+~]|"+G,"i")},T=function(a){var b=q.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},U=T(function(a){return a.appendChild(q.createComment("")),!a.getElementsByTagName("*").length}),V=T(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),W=T(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),X=T(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),Y=T(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",r.insertBefore(a,r.firstChild);var b=q.getElementsByName&&q.getElementsByName(o).length===2+q.getElementsByName(o+0).length;return e=!q.getElementById(o),r.removeChild(a),b});try{t.call(r.childNodes,0)[0].nodeType}catch(Z){t=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}$.matches=function(a,b){return $(a,null,null,b)},$.matchesSelector=function(a,b){return $(b,null,null,[a]).length>0},g=$.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=g(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=g(b);return c},h=$.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},i=$.contains=r.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:r.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},$.attr=function(a,b){var c,d=h(a);return d||(b=b.toLowerCase()),f.attrHandle[b]?f.attrHandle[b](a):W||d?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},f=$.selectors={cacheLength:50,createPseudo:v,match:S,order:new RegExp("ID|TAG"+(Y?"|NAME":"")+(X?"|CLASS":"")),attrHandle:V?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:e?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:U?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(R,""),a[3]=(a[4]||a[5]||"").replace(R,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||$.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&$.error(a[0]),a},PSEUDO:function(a,b,c){var d,e;if(S.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(d=a[4])K.test(d)&&(e=bc(d,b,c,!0))&&(e=d.indexOf(")",d.length-e)-d.length)&&(d=d.slice(0,e),a[0]=a[0].slice(0,e)),a[2]=d;return a.slice(0,3)}},filter:{ID:e?function(a){return a=a.replace(R,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(R,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(R,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=x[o][a];return b||(b=x(a,new RegExp("(^|"+A+")"+a+"("+A+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return b?function(d){var e=$.attr(d,a),f=e+"";if(e==null)return b==="!=";switch(b){case"=":return f===c;case"!=":return f!==c;case"^=":return c&&f.indexOf(c)===0;case"*=":return c&&f.indexOf(c)>-1;case"$=":return c&&f.substr(f.length-c.length)===c;case"~=":return(" "+f+" ").indexOf(c)>-1;case"|=":return f===c||f.substr(0,c.length+1)===c+"-"}}:function(b){return $.attr(b,a)!=null}},CHILD:function(a,b,c,d){if(a==="nth"){var e=s++;return function(a){var b,f,g=0,h=a;if(c===1&&d===0)return!0;b=a.parentNode;if(b&&(b[o]!==e||!a.sizset)){for(h=b.firstChild;h;h=h.nextSibling)if(h.nodeType===1){h.sizset=++g;if(h===a)break}b[o]=e}return f=a.sizset-d,c===0?f===0:f%c===0&&f/c>=0}}return function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b,c,d){var e,g=f.pseudos[a]||f.pseudos[a.toLowerCase()];return g||$.error("unsupported pseudo: "+a),g[o]?g(b,c,d):g.length>1?(e=[a,a,"",b],function(a){return g(a,0,e)}):g}},pseudos:{not:v(function(a,b,c){var d=j(a.replace(H,"$1"),b,c);return function(a){return!d(a)}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!f.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},contains:v(function(a){return function(b){return(b.textContent||b.innerText||g(b)).indexOf(a)>-1}}),has:v(function(a){return function(b){return $(a,b).length>0}}),header:function(a){return P.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:_("radio"),checkbox:_("checkbox"),file:_("file"),password:_("password"),image:_("image"),submit:ba("submit"),reset:ba("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return Q.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b,c){return c?a.slice(1):[a[0]]},last:function(a,b,c){var d=a.pop();return c?a:[d]},even:function(a,b,c){var d=[],e=c?1:0,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},odd:function(a,b,c){var d=[],e=c?0:1,f=a.length;for(;e<f;e=e+2)d.push(a[e]);return d},lt:function(a,b,c){return c?a.slice(+b):a.slice(0,+b)},gt:function(a,b,c){return c?a.slice(0,+b+1):a.slice(+b+1)},eq:function(a,b,c){var d=a.splice(+b,1);return c?a:d}}},k=r.compareDocumentPosition?function(a,b){return a===b?(l=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return l=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bb(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bb(e[j],f[j]);return j===c?bb(a,f[j],-1):bb(e[j],b,1)},[0,0].sort(k),m=!l,$.uniqueSort=function(a){var b,c=1;l=m,a.sort(k);if(l)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},$.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},j=$.compile=function(a,b,c){var d,e,f,g=z[o][a];if(g&&g.context===b)return g;d=bc(a,b,c);for(e=0,f=d.length;e<f;e++)d[e]=bf(d[e],b,c);return g=z(a,bg(d)),g.context=b,g.runs=g.dirruns=0,g},q.querySelectorAll&&function(){var a,b=bk,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[],f=[":active"],g=r.matchesSelector||r.mozMatchesSelector||r.webkitMatchesSelector||r.oMatchesSelector||r.msMatchesSelector;T(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+A+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),T(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+A+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=e.length&&new RegExp(e.join("|")),bk=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a)))if(d.nodeType===9)try{return u.apply(f,t.call(d.querySelectorAll(a),0)),f}catch(i){}else if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){var j,k,l,m=d.getAttribute("id"),n=m||o,p=N.test(a)&&d.parentNode||d;m?n=n.replace(c,"\\$&"):d.setAttribute("id",n),j=bc(a,d,h),n="[id='"+n+"']";for(k=0,l=j.length;k<l;k++)j[k]=n+j[k].selector;try{return u.apply(f,t.call(p.querySelectorAll(j.join(",")),0)),f}catch(i){}finally{m||d.removeAttribute("id")}}return b(a,d,f,g,h)},g&&(T(function(b){a=g.call(b,"div");try{g.call(b,"[test!='']:sizzle"),f.push(S.PSEUDO.source,S.POS.source,"!=")}catch(c){}}),f=new RegExp(f.join("|")),$.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!h(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=g.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return $(c,null,null,[b]).length>0})}(),f.setFilters.nth=f.setFilters.eq,f.filters=f.pseudos,$.attr=p.attr,p.find=$,p.expr=$.selectors,p.expr[":"]=p.expr.pseudos,p.unique=$.uniqueSort,p.text=$.getText,p.isXMLDoc=$.isXML,p.contains=$.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{cj=f.href}catch(cy){cj=e.createElement("a"),cj.href="",cj=cj.href}ck=ct.exec(cj.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:cj,isLocal:cn.test(ck[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=""+(c||y),k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,ck[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase()),l.crossDomain=!(!i||i[1]==ck[1]&&i[2]==ck[2]&&(i[3]||(i[1]==="http:"?80:443))==(ck[3]||(ck[1]==="http:"?80:443)))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e,f=this.createTween(a,b),g=cQ.exec(b),h=f.cur(),i=+h||0,j=1;if(g){c=+g[2],d=g[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&i){i=p.css(f.elem,a,!0)||c||1;do e=j=j||".5",i=i/j,p.style(f.elem,a,i+d),j=f.cur()/h;while(j!==1&&j!==e)}f.unit=d,f.start=i,f.end=g[1]?i+(g[1]+1)*c:c}return f}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j,k,l,m=this[0],n=m&&m.ownerDocument;if(!n)return;return(e=n.body)===m?p.offset.bodyOffset(m):(d=n.documentElement,p.contains(d,m)?(c=m.getBoundingClientRect(),f=da(n),g=d.clientTop||e.clientTop||0,h=d.clientLeft||e.clientLeft||0,i=f.pageYOffset||d.scrollTop,j=f.pageXOffset||d.scrollLeft,k=c.top+i-g,l=c.left+j-h,{top:k,left:l}):{top:0,left:0})},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file
diff --git a/rest_framework/static/rest_framework/js/prettify-min.js b/rest_framework/static/rest_framework/js/prettify-min.js
new file mode 100644
index 00000000..eef5ad7e
--- /dev/null
+++ b/rest_framework/static/rest_framework/js/prettify-min.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
diff --git a/djangorestframework/status.py b/rest_framework/status.py
index 684c9b38..f3a5e481 100644
--- a/djangorestframework/status.py
+++ b/rest_framework/status.py
@@ -1,8 +1,8 @@
"""
Descriptive HTTP status codes, for code readability.
-See RFC 2616 - Sec 10: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
-Also see django.core.handlers.wsgi.STATUS_CODE_TEXT
+See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+And RFC 6585 - http://tools.ietf.org/html/rfc6585
"""
HTTP_100_CONTINUE = 100
@@ -40,9 +40,13 @@ HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
+HTTP_428_PRECONDITION_REQUIRED = 428
+HTTP_429_TOO_MANY_REQUESTS = 429
+HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
+HTTP_511_NETWORD_AUTHENTICATION_REQUIRED = 511
diff --git a/djangorestframework/templates/djangorestframework/api.html b/rest_framework/templates/rest_framework/api.html
index fd9bcc98..81d277e9 100644
--- a/djangorestframework/templates/djangorestframework/api.html
+++ b/rest_framework/templates/rest_framework/api.html
@@ -1,3 +1,3 @@
-{% extends "djangorestframework/base.html" %}
+{% extends "rest_framework/base.html" %}
-{# Override this template in your own templates directory to customize #} \ No newline at end of file
+{# Override this template in your own templates directory to customize #}
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
new file mode 100644
index 00000000..e0f79481
--- /dev/null
+++ b/rest_framework/templates/rest_framework/base.html
@@ -0,0 +1,204 @@
+{% load url from future %}
+{% load rest_framework %}
+{% load static %}
+<!DOCTYPE html>
+<html>
+ <head>
+ {% block head %}
+
+ {% block meta %}
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="robots" content="NONE,NOARCHIVE" />
+ {% endblock %}
+
+ <title>{% block title %}Django REST framework{% endblock %}</title>
+
+ {% block style %}
+ <link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap.min.css"/>
+ <link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap-tweaks.css"/>
+ <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/prettify.css'/>
+ <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/default.css'/>
+ {% endblock %}
+
+ {% endblock %}
+ </head>
+
+ <body class="{% block bodyclass %}{% endblock %} container">
+
+ <div class="wrapper">
+
+ {% block navbar %}
+ <div class="navbar {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}">
+ <div class="navbar-inner">
+ <div class="container-fluid">
+ <span class="brand" href="/">
+ {% block branding %}<a href='http://django-rest-framework.org'>Django REST framework <span class="version">{{ version }}</span></a>{% endblock %}
+ </span>
+ <ul class="nav pull-right">
+ {% block userlinks %}
+ {% if user.is_authenticated %}
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+ {{ user }}
+ <b class="caret"></b>
+ </a>
+ <ul class="dropdown-menu">
+ <li>{% optional_logout request %}</li>
+ </ul>
+ </li>
+ {% else %}
+ <li>{% optional_login request %}</li>
+ {% endif %}
+ {% endblock %}
+ </ul>
+ </div>
+ </div>
+ </div>
+ {% endblock %}
+
+ {% block breadcrumbs %}
+ <ul class="breadcrumb">
+ {% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
+ <li>
+ <a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>{{ breadcrumb_name }}</a> {% if not forloop.last %}<span class="divider">&rsaquo;</span>{% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
+
+ <!-- Content -->
+ <div id="content">
+
+ {% if 'GET' in allowed_methods %}
+ <form id="get-form" class="pull-right">
+ <fieldset>
+ <div class="btn-group format-selection">
+ <a class="btn btn-primary js-tooltip" href='{{ request.get_full_path }}' rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a>
+
+ <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown" title="Specify a format for the GET request">
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ {% for format in available_formats %}
+ <li>
+ <a class="js-tooltip format-option" href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}' rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+
+ </fieldset>
+ </form>
+ {% endif %}
+
+ {% if options_form %}
+ <form class="button-form" action="{{ request.get_full_path }}" method="POST" class="pull-right">
+ {% csrf_token %}
+ <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
+ <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
+ </form>
+ {% endif %}
+
+ {% if delete_form %}
+ <form class="button-form" action="{{ request.get_full_path }}" method="POST" class="pull-right">
+ {% csrf_token %}
+ <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
+ <button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button>
+ </form>
+ {% endif %}
+
+ <div class="content-main">
+ <div class="page-header"><h1>{{ name }}</h1></div>
+ {{ description }}
+
+ <div class="request-info">
+ <pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre>
+ <div>
+ <div class="response-info">
+ <pre class="prettyprint"><div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
+{% for key, val in response.items %}<b>{{ key }}:</b> <span class="lit">{{ val|urlize_quoted_links }}</span>
+{% endfor %}
+</div>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
+ </div>
+ </div>
+
+ {% if response.status_code != 403 %}
+
+ {% if post_form %}
+ <div class="well">
+ <form action="{{ request.get_full_path }}" method="POST" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %} class="form-horizontal">
+ <fieldset>
+ {% csrf_token %}
+ {{ post_form.non_field_errors }}
+ {% for field in post_form %}
+ <div class="control-group {% if field.errors %}error{% endif %}">
+ {{ field.label_tag|add_class:"control-label" }}
+ <div class="controls">
+ {{ field|add_class:"input-xlarge" }}
+ <span class="help-inline">{{ field.help_text }}</span>
+ {{ field.errors|add_class:"help-block" }}
+ </div>
+ </div>
+ {% endfor %}
+ <div class="form-actions">
+ <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ {% endif %}
+
+ {% if put_form %}
+ <div class="well">
+ <form action="{{ request.get_full_path }}" method="POST" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %} class="form-horizontal">
+ <fieldset>
+ <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" />
+ {% csrf_token %}
+ {{ put_form.non_field_errors }}
+ {% for field in put_form %}
+ <div class="control-group {% if field.errors %}error{% endif %}">
+ {{ field.label_tag|add_class:"control-label" }}
+ <div class="controls">
+ {{ field|add_class:"input-xlarge" }}
+ <span class='help-inline'>{{ field.help_text }}</span>
+ {{ field.errors|add_class:"help-block" }}
+ </div>
+ </div>
+ {% endfor %}
+ <div class="form-actions">
+ <button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
+ </div>
+
+ </fieldset>
+ </form>
+ </div>
+ {% endif %}
+
+ {% endif %}
+
+ </div>
+ <!-- END content-main -->
+
+ </div>
+ <!-- END Content -->
+
+ <div id="push"></div>
+
+ </div>
+
+ </div><!-- ./wrapper -->
+
+ {% block footer %}
+ <!--<div id="footer">
+ <a class="powered-by" href='http://django-rest-framework.org'>Django REST framework</a>
+ </div>-->
+ {% endblock %}
+
+ {% block script %}
+ <script src="{% get_static_prefix %}rest_framework/js/jquery-1.8.1-min.js"></script>
+ <script src="{% get_static_prefix %}rest_framework/js/bootstrap.min.js"></script>
+ <script src="{% get_static_prefix %}rest_framework/js/prettify-min.js"></script>
+ <script src="{% get_static_prefix %}rest_framework/js/default.js"></script>
+ {% endblock %}
+ </body>
+</html>
diff --git a/rest_framework/templates/rest_framework/login.html b/rest_framework/templates/rest_framework/login.html
new file mode 100644
index 00000000..c1271399
--- /dev/null
+++ b/rest_framework/templates/rest_framework/login.html
@@ -0,0 +1,53 @@
+{% load url from future %}
+{% load static %}
+<html>
+
+ <head>
+ <link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap.min.css"/>
+ <link rel="stylesheet" type="text/css" href="{% get_static_prefix %}rest_framework/css/bootstrap-tweaks.css"/>
+ <link rel="stylesheet" type="text/css" href='{% get_static_prefix %}rest_framework/css/default.css'/>
+ </head>
+
+ <body class="container">
+
+<div class="container-fluid" style="margin-top: 30px">
+ <div class="row-fluid">
+
+ <div class="well" style="width: 320px; margin-left: auto; margin-right: auto">
+ <div class="row-fluid">
+ <div>
+ <h3 style="margin: 0 0 20px;">Django REST framework</h3>
+ </div>
+ </div><!-- /row fluid -->
+
+ <div class="row-fluid">
+ <div>
+ <form action="{% url 'rest_framework:login' %}" class=" form-inline" method="post">
+ {% csrf_token %}
+ <div id="div_id_username" class="clearfix control-group">
+ <div class="controls" style="height: 30px">
+ <Label class="span4" style="margin-top: 3px">Username:</label>
+ <input style="height: 25px" type="text" name="username" maxlength="100" autocapitalize="off" autocorrect="off" class="textinput textInput" id="id_username">
+ </div>
+ </div>
+ <div id="div_id_password" class="clearfix control-group">
+ <div class="controls" style="height: 30px">
+ <Label class="span4" style="margin-top: 3px">Password:</label>
+ <input style="height: 25px" type="password" name="password" maxlength="100" autocapitalize="off" autocorrect="off" class="textinput textInput" id="id_password">
+ </div>
+ </div>
+ <input type="hidden" name="next" value="{{ next }}" />
+ <div class="form-actions-no-box">
+ <input type="submit" name="submit" value="Log in" class="btn btn-primary" id="submit-id-submit">
+ </div>
+ </form>
+ </div>
+ </div><!-- /row fluid -->
+ </div><!--/span-->
+
+ </div><!-- /.row-fluid -->
+ </div>
+
+ </div>
+ </body>
+</html>
diff --git a/examples/mixin/__init__.py b/rest_framework/templatetags/__init__.py
index e69de29b..e69de29b 100644
--- a/examples/mixin/__init__.py
+++ b/rest_framework/templatetags/__init__.py
diff --git a/djangorestframework/templatetags/urlize_quoted_links.py b/rest_framework/templatetags/rest_framework.py
index ffe859c9..c9b6eb10 100644
--- a/djangorestframework/templatetags/urlize_quoted_links.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -1,27 +1,24 @@
-"""Adds the custom filter 'urlize_quoted_links'
-
-This is identical to the built-in filter 'urlize' with the exception that
-single and double quotes are permitted as leading or trailing punctuation.
-"""
-
-# Almost all of this code is copied verbatim from django.utils.html
-# LEADING_PUNCTUATION and TRAILING_PUNCTUATION have been modified
+from django import template
+from django.core.urlresolvers import reverse
+from django.http import QueryDict
+from django.utils.encoding import force_unicode
+from django.utils.html import escape
+from django.utils.safestring import SafeData, mark_safe
+from urlparse import urlsplit, urlunsplit
import re
import string
-from django.utils.safestring import SafeData, mark_safe
-from django.utils.encoding import force_unicode
-from django.utils.http import urlquote
-from django.utils.html import escape
-from django import template
+register = template.Library()
-# Configuration for urlize() function.
-LEADING_PUNCTUATION = ['(', '<', '&lt;', '"', "'"]
-TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;', '"', "'"]
-# List of possible strings used for bullets in bulleted lists.
-DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
+# Regex for adding classes to html snippets
+class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
+
+# Bunch of stuff cloned from urlize
+LEADING_PUNCTUATION = ['(', '<', '&lt;', '"', "'"]
+TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;', '"', "'"]
+DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
word_split_re = re.compile(r'(\s+)')
punctuation_re = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
@@ -33,6 +30,88 @@ html_gunk_re = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<st
hard_coded_bullets_re = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(x) for x in DOTS]), re.DOTALL)
trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
+
+# Helper function for 'add_query_param'
+def replace_query_param(url, key, val):
+ """
+ Given a URL and a key/val pair, set or replace an item in the query
+ parameters of the URL, and return the new URL.
+ """
+ (scheme, netloc, path, query, fragment) = urlsplit(url)
+ query_dict = QueryDict(query).copy()
+ query_dict[key] = val
+ query = query_dict.urlencode()
+ return urlunsplit((scheme, netloc, path, query, fragment))
+
+
+# And the template tags themselves...
+
+@register.simple_tag
+def optional_login(request):
+ """
+ Include a login snippet if REST framework's login view is in the URLconf.
+ """
+ try:
+ login_url = reverse('rest_framework:login')
+ except:
+ return ''
+
+ snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, request.path)
+ return snippet
+
+
+@register.simple_tag
+def optional_logout(request):
+ """
+ Include a logout snippet if REST framework's logout view is in the URLconf.
+ """
+ try:
+ logout_url = reverse('rest_framework:logout')
+ except:
+ return ''
+
+ snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, request.path)
+ return snippet
+
+
+@register.simple_tag
+def add_query_param(request, key, val):
+ """
+ Add a query parameter to the current request url, and return the new url.
+ """
+ return replace_query_param(request.get_full_path(), key, val)
+
+
+@register.filter
+def add_class(value, css_class):
+ """
+ http://stackoverflow.com/questions/4124220/django-adding-css-classes-when-rendering-form-fields-in-a-template
+
+ Inserts classes into template variables that contain HTML tags,
+ useful for modifying forms without needing to change the Form objects.
+
+ Usage:
+
+ {{ field.label_tag|add_class:"control-label" }}
+
+ In the case of REST Framework, the filter is used to add Bootstrap-specific
+ classes to the forms.
+ """
+ html = unicode(value)
+ match = class_re.search(html)
+ if match:
+ m = re.search(r'^%s$|^%s\s|\s%s\s|\s%s$' % (css_class, css_class,
+ css_class, css_class),
+ match.group(1))
+ if not m:
+ return mark_safe(class_re.sub(match.group(1) + " " + css_class,
+ html))
+ else:
+ return mark_safe(html.replace('>', ' class="%s">' % css_class, 1))
+ return value
+
+
+@register.filter
def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True):
"""
Converts any URLs in text into clickable links.
@@ -88,13 +167,4 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
words[i] = mark_safe(word)
elif autoescape:
words[i] = escape(word)
- return u''.join(words)
-
-
-#urlize_quoted_links.needs_autoescape = True
-urlize_quoted_links.is_safe = True
-
-# Register urlize_quoted_links as a custom filter
-# http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
-register = template.Library()
-register.filter(urlize_quoted_links)
+ return mark_safe(u''.join(words))
diff --git a/examples/modelresourceexample/__init__.py b/rest_framework/tests/__init__.py
index e69de29b..e69de29b 100644
--- a/examples/modelresourceexample/__init__.py
+++ b/rest_framework/tests/__init__.py
diff --git a/djangorestframework/tests/authentication.py b/rest_framework/tests/authentication.py
index 3dac7ee3..8ab4c4e4 100644
--- a/djangorestframework/tests/authentication.py
+++ b/rest_framework/tests/authentication.py
@@ -3,21 +3,27 @@ from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.utils import simplejson as json
+from django.http import HttpResponse
-from djangorestframework.views import View
-from djangorestframework import permissions
+from rest_framework.views import APIView
+from rest_framework import permissions
+
+from rest_framework.authtoken.models import Token
+from rest_framework.authentication import TokenAuthentication
import base64
-class MockView(View):
- permissions = (permissions.IsAuthenticated,)
+class MockView(APIView):
+ permission_classes = (permissions.IsAuthenticated,)
def post(self, request):
- return {'a': 1, 'b': 2, 'c': 3}
+ return HttpResponse({'a': 1, 'b': 2, 'c': 3})
def put(self, request):
- return {'a': 1, 'b': 2, 'c': 3}
+ return HttpResponse({'a': 1, 'b': 2, 'c': 3})
+
+MockView.authentication_classes += (TokenAuthentication,)
urlpatterns = patterns('',
(r'^$', MockView.as_view()),
@@ -26,7 +32,7 @@ urlpatterns = patterns('',
class BasicAuthTests(TestCase):
"""Basic authentication"""
- urls = 'djangorestframework.tests.authentication'
+ urls = 'rest_framework.tests.authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
@@ -60,7 +66,7 @@ class BasicAuthTests(TestCase):
class SessionAuthTests(TestCase):
"""User session authentication"""
- urls = 'djangorestframework.tests.authentication'
+ urls = 'rest_framework.tests.authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
@@ -89,13 +95,13 @@ class SessionAuthTests(TestCase):
response = self.non_csrf_client.post('/', {'example': 'example'})
self.assertEqual(response.status_code, 200)
- # def test_put_form_session_auth_passing(self):
- # """
- # Ensure PUTting form over session authentication with logged in user and CSRF token passes.
- # """
- # self.non_csrf_client.login(username=self.username, password=self.password)
- # response = self.non_csrf_client.put('/', {'example': 'example'})
- # self.assertEqual(response.status_code, 200)
+ def test_put_form_session_auth_passing(self):
+ """
+ Ensure PUTting form over session authentication with logged in user and CSRF token passes.
+ """
+ self.non_csrf_client.login(username=self.username, password=self.password)
+ response = self.non_csrf_client.put('/', {'example': 'example'})
+ self.assertEqual(response.status_code, 200)
def test_post_form_session_auth_failing(self):
"""
@@ -103,3 +109,46 @@ class SessionAuthTests(TestCase):
"""
response = self.csrf_client.post('/', {'example': 'example'})
self.assertEqual(response.status_code, 403)
+
+
+class TokenAuthTests(TestCase):
+ """Token authentication"""
+ urls = 'rest_framework.tests.authentication'
+
+ def setUp(self):
+ self.csrf_client = Client(enforce_csrf_checks=True)
+ self.username = 'john'
+ self.email = 'lennon@thebeatles.com'
+ self.password = 'password'
+ self.user = User.objects.create_user(self.username, self.email, self.password)
+
+ self.key = 'abcd1234'
+ self.token = Token.objects.create(key=self.key, user=self.user)
+
+ def test_post_form_passing_token_auth(self):
+ """Ensure POSTing json over token auth with correct credentials passes and does not require CSRF"""
+ auth = "Token " + self.key
+ response = self.csrf_client.post('/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
+ self.assertEqual(response.status_code, 200)
+
+ def test_post_json_passing_token_auth(self):
+ """Ensure POSTing form over token auth with correct credentials passes and does not require CSRF"""
+ auth = "Token " + self.key
+ response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
+ self.assertEqual(response.status_code, 200)
+
+ def test_post_form_failing_token_auth(self):
+ """Ensure POSTing form over token auth without correct credentials fails"""
+ response = self.csrf_client.post('/', {'example': 'example'})
+ self.assertEqual(response.status_code, 403)
+
+ def test_post_json_failing_token_auth(self):
+ """Ensure POSTing json over token auth without correct credentials fails"""
+ response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json')
+ self.assertEqual(response.status_code, 403)
+
+ def test_token_has_auto_assigned_key_if_none_provided(self):
+ """Ensure creating a token with no key will auto-assign a key"""
+ self.token.delete()
+ token = Token.objects.create(user=self.user)
+ self.assertTrue(bool(token.key))
diff --git a/djangorestframework/tests/breadcrumbs.py b/rest_framework/tests/breadcrumbs.py
index d7473c6a..647ab96d 100644
--- a/djangorestframework/tests/breadcrumbs.py
+++ b/rest_framework/tests/breadcrumbs.py
@@ -1,21 +1,26 @@
from django.conf.urls.defaults import patterns, url
from django.test import TestCase
-from djangorestframework.utils.breadcrumbs import get_breadcrumbs
-from djangorestframework.views import View
+from rest_framework.utils.breadcrumbs import get_breadcrumbs
+from rest_framework.views import APIView
-class Root(View):
+
+class Root(APIView):
pass
-class ResourceRoot(View):
+
+class ResourceRoot(APIView):
pass
-class ResourceInstance(View):
+
+class ResourceInstance(APIView):
pass
-class NestedResourceRoot(View):
+
+class NestedResourceRoot(APIView):
pass
-class NestedResourceInstance(View):
+
+class NestedResourceInstance(APIView):
pass
urlpatterns = patterns('',
@@ -30,7 +35,7 @@ urlpatterns = patterns('',
class BreadcrumbTests(TestCase):
"""Tests the breadcrumb functionality used by the HTML renderer."""
- urls = 'djangorestframework.tests.breadcrumbs'
+ urls = 'rest_framework.tests.breadcrumbs'
def test_root_breadcrumbs(self):
url = '/'
diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/decorators.py
new file mode 100644
index 00000000..41864d71
--- /dev/null
+++ b/rest_framework/tests/decorators.py
@@ -0,0 +1,128 @@
+from django.test import TestCase
+from rest_framework import status
+from rest_framework.response import Response
+from django.test.client import RequestFactory
+from rest_framework.renderers import JSONRenderer
+from rest_framework.parsers import JSONParser
+from rest_framework.authentication import BasicAuthentication
+from rest_framework.throttling import UserRateThrottle
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.views import APIView
+from rest_framework.decorators import (
+ api_view,
+ renderer_classes,
+ parser_classes,
+ authentication_classes,
+ throttle_classes,
+ permission_classes,
+)
+
+
+class DecoratorTestCase(TestCase):
+
+ def setUp(self):
+ self.factory = RequestFactory()
+
+ def _finalize_response(self, request, response, *args, **kwargs):
+ response.request = request
+ return APIView.finalize_response(self, request, response, *args, **kwargs)
+
+ def test_wrap_view(self):
+
+ @api_view(['GET'])
+ def view(request):
+ return Response({})
+
+ self.assertTrue(isinstance(view.cls_instance, APIView))
+
+ def test_calling_method(self):
+
+ @api_view(['GET'])
+ def view(request):
+ return Response({})
+
+ request = self.factory.get('/')
+ response = view(request)
+ self.assertEqual(response.status_code, 200)
+
+ request = self.factory.post('/')
+ response = view(request)
+ self.assertEqual(response.status_code, 405)
+
+ def test_calling_put_method(self):
+
+ @api_view(['GET', 'PUT'])
+ def view(request):
+ return Response({})
+
+ request = self.factory.put('/')
+ response = view(request)
+ self.assertEqual(response.status_code, 200)
+
+ request = self.factory.post('/')
+ response = view(request)
+ self.assertEqual(response.status_code, 405)
+
+ def test_renderer_classes(self):
+
+ @api_view(['GET'])
+ @renderer_classes([JSONRenderer])
+ def view(request):
+ return Response({})
+
+ request = self.factory.get('/')
+ response = view(request)
+ self.assertTrue(isinstance(response.accepted_renderer, JSONRenderer))
+
+ def test_parser_classes(self):
+
+ @api_view(['GET'])
+ @parser_classes([JSONParser])
+ def view(request):
+ self.assertEqual(len(request.parsers), 1)
+ self.assertTrue(isinstance(request.parsers[0],
+ JSONParser))
+ return Response({})
+
+ request = self.factory.get('/')
+ view(request)
+
+ def test_authentication_classes(self):
+
+ @api_view(['GET'])
+ @authentication_classes([BasicAuthentication])
+ def view(request):
+ self.assertEqual(len(request.authenticators), 1)
+ self.assertTrue(isinstance(request.authenticators[0],
+ BasicAuthentication))
+ return Response({})
+
+ request = self.factory.get('/')
+ view(request)
+
+ def test_permission_classes(self):
+
+ @api_view(['GET'])
+ @permission_classes([IsAuthenticated])
+ def view(request):
+ return Response({})
+
+ request = self.factory.get('/')
+ response = view(request)
+ self.assertEquals(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_throttle_classes(self):
+ class OncePerDayUserThrottle(UserRateThrottle):
+ rate = '1/day'
+
+ @api_view(['GET'])
+ @throttle_classes([OncePerDayUserThrottle])
+ def view(request):
+ return Response({})
+
+ request = self.factory.get('/')
+ response = view(request)
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+
+ response = view(request)
+ self.assertEquals(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
diff --git a/djangorestframework/tests/description.py b/rest_framework/tests/description.py
index 212b4ca4..d958b840 100644
--- a/djangorestframework/tests/description.py
+++ b/rest_framework/tests/description.py
@@ -1,6 +1,6 @@
from django.test import TestCase
-from djangorestframework.views import View
-from djangorestframework.compat import apply_markdown
+from rest_framework.views import APIView
+from rest_framework.compat import apply_markdown
# We check that docstrings get nicely un-indented.
DESCRIPTION = """an example docstring
@@ -48,21 +48,21 @@ MARKED_DOWN_gte_21 = """<h2 id="an-example-docstring">an example docstring</h2>
class TestViewNamesAndDescriptions(TestCase):
def test_resource_name_uses_classname_by_default(self):
"""Ensure Resource names are based on the classname by default."""
- class MockView(View):
+ class MockView(APIView):
pass
self.assertEquals(MockView().get_name(), 'Mock')
def test_resource_name_can_be_set_explicitly(self):
"""Ensure Resource names can be set using the 'get_name' method."""
example = 'Some Other Name'
- class MockView(View):
+ class MockView(APIView):
def get_name(self):
return example
self.assertEquals(MockView().get_name(), example)
def test_resource_description_uses_docstring_by_default(self):
"""Ensure Resource names are based on the docstring by default."""
- class MockView(View):
+ class MockView(APIView):
"""an example docstring
====================
@@ -83,7 +83,8 @@ class TestViewNamesAndDescriptions(TestCase):
def test_resource_description_can_be_set_explicitly(self):
"""Ensure Resource descriptions can be set using the 'get_description' method."""
example = 'Some other description'
- class MockView(View):
+
+ class MockView(APIView):
"""docstring"""
def get_description(self):
return example
@@ -92,14 +93,15 @@ class TestViewNamesAndDescriptions(TestCase):
def test_resource_description_does_not_require_docstring(self):
"""Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'get_description' method."""
example = 'Some other description'
- class MockView(View):
+
+ class MockView(APIView):
def get_description(self):
return example
self.assertEquals(MockView().get_description(), example)
def test_resource_description_can_be_empty(self):
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string."""
- class MockView(View):
+ class MockView(APIView):
pass
self.assertEquals(MockView().get_description(), '')
diff --git a/rest_framework/tests/files.py b/rest_framework/tests/files.py
new file mode 100644
index 00000000..61d7f7b1
--- /dev/null
+++ b/rest_framework/tests/files.py
@@ -0,0 +1,34 @@
+# from django.test import TestCase
+# from django import forms
+
+# from django.test.client import RequestFactory
+# from rest_framework.views import View
+# from rest_framework.response import Response
+
+# import StringIO
+
+
+# class UploadFilesTests(TestCase):
+# """Check uploading of files"""
+# def setUp(self):
+# self.factory = RequestFactory()
+
+# def test_upload_file(self):
+
+# class FileForm(forms.Form):
+# file = forms.FileField()
+
+# class MockView(View):
+# permissions = ()
+# form = FileForm
+
+# def post(self, request, *args, **kwargs):
+# return Response({'FILE_NAME': self.CONTENT['file'].name,
+# 'FILE_CONTENT': self.CONTENT['file'].read()})
+
+# file = StringIO.StringIO('stuff')
+# file.name = 'stuff.txt'
+# request = self.factory.post('/', {'file': file})
+# view = MockView.as_view()
+# response = view(request)
+# self.assertEquals(response.raw_content, {"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"})
diff --git a/rest_framework/tests/genericrelations.py b/rest_framework/tests/genericrelations.py
new file mode 100644
index 00000000..1d7e33bc
--- /dev/null
+++ b/rest_framework/tests/genericrelations.py
@@ -0,0 +1,33 @@
+from django.test import TestCase
+from rest_framework import serializers
+from rest_framework.tests.models import *
+
+
+class TestGenericRelations(TestCase):
+ def setUp(self):
+ bookmark = Bookmark(url='https://www.djangoproject.com/')
+ bookmark.save()
+ django = Tag(tag_name='django')
+ django.save()
+ python = Tag(tag_name='python')
+ python.save()
+ t1 = TaggedItem(content_object=bookmark, tag=django)
+ t1.save()
+ t2 = TaggedItem(content_object=bookmark, tag=python)
+ t2.save()
+ self.bookmark = bookmark
+
+ def test_reverse_generic_relation(self):
+ class BookmarkSerializer(serializers.ModelSerializer):
+ tags = serializers.ManyRelatedField(source='tags')
+
+ class Meta:
+ model = Bookmark
+ exclude = ('id',)
+
+ serializer = BookmarkSerializer(instance=self.bookmark)
+ expected = {
+ 'tags': [u'django', u'python'],
+ 'url': u'https://www.djangoproject.com/'
+ }
+ self.assertEquals(serializer.data, expected)
diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py
new file mode 100644
index 00000000..d45ea976
--- /dev/null
+++ b/rest_framework/tests/generics.py
@@ -0,0 +1,303 @@
+from django.test import TestCase
+from django.test.client import RequestFactory
+from django.utils import simplejson as json
+from rest_framework import generics, serializers, status
+from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel
+
+
+factory = RequestFactory()
+
+
+class RootView(generics.ListCreateAPIView):
+ """
+ Example description for OPTIONS.
+ """
+ model = BasicModel
+
+
+class InstanceView(generics.RetrieveUpdateDestroyAPIView):
+ """
+ Example description for OPTIONS.
+ """
+ model = BasicModel
+
+
+class SlugSerializer(serializers.ModelSerializer):
+ slug = serializers.Field() # read only
+
+ class Meta:
+ model = SlugBasedModel
+ exclude = ('id',)
+
+
+class SlugBasedInstanceView(InstanceView):
+ """
+ A model with a slug-field.
+ """
+ model = SlugBasedModel
+ serializer_class = SlugSerializer
+
+
+class TestRootView(TestCase):
+ def setUp(self):
+ """
+ Create 3 BasicModel intances.
+ """
+ items = ['foo', 'bar', 'baz']
+ for item in items:
+ BasicModel(text=item).save()
+ self.objects = BasicModel.objects
+ self.data = [
+ {'id': obj.id, 'text': obj.text}
+ for obj in self.objects.all()
+ ]
+ self.view = RootView.as_view()
+
+ def test_get_root_view(self):
+ """
+ GET requests to ListCreateAPIView should return list of objects.
+ """
+ request = factory.get('/')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, self.data)
+
+ def test_post_root_view(self):
+ """
+ POST requests to ListCreateAPIView should create a new object.
+ """
+ content = {'text': 'foobar'}
+ request = factory.post('/', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_201_CREATED)
+ self.assertEquals(response.data, {'id': 4, 'text': u'foobar'})
+ created = self.objects.get(id=4)
+ self.assertEquals(created.text, 'foobar')
+
+ def test_put_root_view(self):
+ """
+ PUT requests to ListCreateAPIView should not be allowed
+ """
+ content = {'text': 'foobar'}
+ request = factory.put('/', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ self.assertEquals(response.data, {"detail": "Method 'PUT' not allowed."})
+
+ def test_delete_root_view(self):
+ """
+ DELETE requests to ListCreateAPIView should not be allowed
+ """
+ request = factory.delete('/')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ self.assertEquals(response.data, {"detail": "Method 'DELETE' not allowed."})
+
+ def test_options_root_view(self):
+ """
+ OPTIONS requests to ListCreateAPIView should return metadata
+ """
+ request = factory.options('/')
+ response = self.view(request).render()
+ expected = {
+ 'parses': [
+ 'application/json',
+ 'application/x-www-form-urlencoded',
+ 'multipart/form-data'
+ ],
+ 'renders': [
+ 'application/json',
+ 'text/html'
+ ],
+ 'name': 'Root',
+ 'description': 'Example description for OPTIONS.'
+ }
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, expected)
+
+ def test_post_cannot_set_id(self):
+ """
+ POST requests to create a new object should not be able to set the id.
+ """
+ content = {'id': 999, 'text': 'foobar'}
+ request = factory.post('/', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_201_CREATED)
+ self.assertEquals(response.data, {'id': 4, 'text': u'foobar'})
+ created = self.objects.get(id=4)
+ self.assertEquals(created.text, 'foobar')
+
+
+class TestInstanceView(TestCase):
+ def setUp(self):
+ """
+ Create 3 BasicModel intances.
+ """
+ items = ['foo', 'bar', 'baz']
+ for item in items:
+ BasicModel(text=item).save()
+ self.objects = BasicModel.objects
+ self.data = [
+ {'id': obj.id, 'text': obj.text}
+ for obj in self.objects.all()
+ ]
+ self.view = InstanceView.as_view()
+ self.slug_based_view = SlugBasedInstanceView.as_view()
+
+ def test_get_instance_view(self):
+ """
+ GET requests to RetrieveUpdateDestroyAPIView should return a single object.
+ """
+ request = factory.get('/1')
+ response = self.view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, self.data[0])
+
+ def test_post_instance_view(self):
+ """
+ POST requests to RetrieveUpdateDestroyAPIView should not be allowed
+ """
+ content = {'text': 'foobar'}
+ request = factory.post('/', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ self.assertEquals(response.data, {"detail": "Method 'POST' not allowed."})
+
+ def test_put_instance_view(self):
+ """
+ PUT requests to RetrieveUpdateDestroyAPIView should update an object.
+ """
+ content = {'text': 'foobar'}
+ request = factory.put('/1', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, {'id': 1, 'text': 'foobar'})
+ updated = self.objects.get(id=1)
+ self.assertEquals(updated.text, 'foobar')
+
+ def test_delete_instance_view(self):
+ """
+ DELETE requests to RetrieveUpdateDestroyAPIView should delete an object.
+ """
+ request = factory.delete('/1')
+ response = self.view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)
+ self.assertEquals(response.content, '')
+ ids = [obj.id for obj in self.objects.all()]
+ self.assertEquals(ids, [2, 3])
+
+ def test_options_instance_view(self):
+ """
+ OPTIONS requests to RetrieveUpdateDestroyAPIView should return metadata
+ """
+ request = factory.options('/')
+ response = self.view(request).render()
+ expected = {
+ 'parses': [
+ 'application/json',
+ 'application/x-www-form-urlencoded',
+ 'multipart/form-data'
+ ],
+ 'renders': [
+ 'application/json',
+ 'text/html'
+ ],
+ 'name': 'Instance',
+ 'description': 'Example description for OPTIONS.'
+ }
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, expected)
+
+ def test_put_cannot_set_id(self):
+ """
+ PUT requests to create a new object should not be able to set the id.
+ """
+ content = {'id': 999, 'text': 'foobar'}
+ request = factory.put('/1', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, {'id': 1, 'text': 'foobar'})
+ updated = self.objects.get(id=1)
+ self.assertEquals(updated.text, 'foobar')
+
+ def test_put_to_deleted_instance(self):
+ """
+ PUT requests to RetrieveUpdateDestroyAPIView should create an object
+ if it does not currently exist.
+ """
+ self.objects.get(id=1).delete()
+ content = {'text': 'foobar'}
+ request = factory.put('/1', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, {'id': 1, 'text': 'foobar'})
+ updated = self.objects.get(id=1)
+ self.assertEquals(updated.text, 'foobar')
+
+ def test_put_as_create_on_id_based_url(self):
+ """
+ PUT requests to RetrieveUpdateDestroyAPIView should create an object
+ at the requested url if it doesn't exist.
+ """
+ content = {'text': 'foobar'}
+ # pk fields can not be created on demand, only the database can set th pk for a new object
+ request = factory.put('/5', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request, pk=5).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ new_obj = self.objects.get(pk=5)
+ self.assertEquals(new_obj.text, 'foobar')
+
+ def test_put_as_create_on_slug_based_url(self):
+ """
+ PUT requests to RetrieveUpdateDestroyAPIView should create an object
+ at the requested url if possible, else return HTTP_403_FORBIDDEN error-response.
+ """
+ content = {'text': 'foobar'}
+ request = factory.put('/test_slug', json.dumps(content),
+ content_type='application/json')
+ response = self.slug_based_view(request, slug='test_slug').render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, {'slug': 'test_slug', 'text': 'foobar'})
+ new_obj = SlugBasedModel.objects.get(slug='test_slug')
+ self.assertEquals(new_obj.text, 'foobar')
+
+
+# Regression test for #285
+
+class CommentSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Comment
+ exclude = ('created',)
+
+
+class CommentView(generics.ListCreateAPIView):
+ serializer_class = CommentSerializer
+ model = Comment
+
+
+class TestCreateModelWithAutoNowAddField(TestCase):
+ def setUp(self):
+ self.objects = Comment.objects
+ self.view = CommentView.as_view()
+
+ def test_create_model_with_auto_now_add_field(self):
+ """
+ Regression test for #285
+
+ https://github.com/tomchristie/django-rest-framework/issues/285
+ """
+ content = {'email': 'foobar@example.com', 'content': 'foobar'}
+ request = factory.post('/', json.dumps(content),
+ content_type='application/json')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_201_CREATED)
+ created = self.objects.get(id=1)
+ self.assertEquals(created.content, 'foobar')
diff --git a/rest_framework/tests/htmlrenderer.py b/rest_framework/tests/htmlrenderer.py
new file mode 100644
index 00000000..10d7e31d
--- /dev/null
+++ b/rest_framework/tests/htmlrenderer.py
@@ -0,0 +1,50 @@
+from django.conf.urls.defaults import patterns, url
+from django.test import TestCase
+from django.template import TemplateDoesNotExist, Template
+import django.template.loader
+from rest_framework.decorators import api_view, renderer_classes
+from rest_framework.renderers import TemplateHTMLRenderer
+from rest_framework.response import Response
+
+
+@api_view(('GET',))
+@renderer_classes((TemplateHTMLRenderer,))
+def example(request):
+ """
+ A view that can returns an HTML representation.
+ """
+ data = {'object': 'foobar'}
+ return Response(data, template_name='example.html')
+
+
+urlpatterns = patterns('',
+ url(r'^$', example),
+)
+
+
+class TemplateHTMLRendererTests(TestCase):
+ urls = 'rest_framework.tests.htmlrenderer'
+
+ def setUp(self):
+ """
+ Monkeypatch get_template
+ """
+ self.get_template = django.template.loader.get_template
+
+ def get_template(template_name):
+ if template_name == 'example.html':
+ return Template("example: {{ object }}")
+ raise TemplateDoesNotExist(template_name)
+
+ django.template.loader.get_template = get_template
+
+ def tearDown(self):
+ """
+ Revert monkeypatching
+ """
+ django.template.loader.get_template = self.get_template
+
+ def test_simple_html_view(self):
+ response = self.client.get('/')
+ self.assertContains(response, "example: foobar")
+ self.assertEquals(response['Content-Type'], 'text/html')
diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py
new file mode 100644
index 00000000..92c3691e
--- /dev/null
+++ b/rest_framework/tests/hyperlinkedserializers.py
@@ -0,0 +1,168 @@
+from django.conf.urls.defaults import patterns, url
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework import generics, status, serializers
+from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment
+
+factory = RequestFactory()
+
+
+class BlogPostCommentSerializer(serializers.Serializer):
+ text = serializers.CharField()
+ blog_post_url = serializers.HyperlinkedRelatedField(source='blog_post', view_name='blogpost-detail', queryset=BlogPost.objects.all())
+
+ def restore_object(self, attrs, instance=None):
+ return BlogPostComment(**attrs)
+
+
+class BasicList(generics.ListCreateAPIView):
+ model = BasicModel
+ model_serializer_class = serializers.HyperlinkedModelSerializer
+
+
+class BasicDetail(generics.RetrieveUpdateDestroyAPIView):
+ model = BasicModel
+ model_serializer_class = serializers.HyperlinkedModelSerializer
+
+
+class AnchorDetail(generics.RetrieveAPIView):
+ model = Anchor
+ model_serializer_class = serializers.HyperlinkedModelSerializer
+
+
+class ManyToManyList(generics.ListAPIView):
+ model = ManyToManyModel
+ model_serializer_class = serializers.HyperlinkedModelSerializer
+
+
+class ManyToManyDetail(generics.RetrieveAPIView):
+ model = ManyToManyModel
+ model_serializer_class = serializers.HyperlinkedModelSerializer
+
+
+class BlogPostCommentListCreate(generics.ListCreateAPIView):
+ model = BlogPostComment
+ model_serializer_class = BlogPostCommentSerializer
+
+
+class BlogPostDetail(generics.RetrieveAPIView):
+ model = BlogPost
+
+urlpatterns = patterns('',
+ url(r'^basic/$', BasicList.as_view(), name='basicmodel-list'),
+ url(r'^basic/(?P<pk>\d+)/$', BasicDetail.as_view(), name='basicmodel-detail'),
+ url(r'^anchor/(?P<pk>\d+)/$', AnchorDetail.as_view(), name='anchor-detail'),
+ url(r'^manytomany/$', ManyToManyList.as_view(), name='manytomanymodel-list'),
+ url(r'^manytomany/(?P<pk>\d+)/$', ManyToManyDetail.as_view(), name='manytomanymodel-detail'),
+ url(r'^posts/(?P<pk>\d+)/$', BlogPostDetail.as_view(), name='blogpost-detail'),
+ url(r'^comments/$', BlogPostCommentListCreate.as_view(), name='blogpostcomment-list')
+)
+
+
+class TestBasicHyperlinkedView(TestCase):
+ urls = 'rest_framework.tests.hyperlinkedserializers'
+
+ def setUp(self):
+ """
+ Create 3 BasicModel intances.
+ """
+ items = ['foo', 'bar', 'baz']
+ for item in items:
+ BasicModel(text=item).save()
+ self.objects = BasicModel.objects
+ self.data = [
+ {'url': 'http://testserver/basic/%d/' % obj.id, 'text': obj.text}
+ for obj in self.objects.all()
+ ]
+ self.list_view = BasicList.as_view()
+ self.detail_view = BasicDetail.as_view()
+
+ def test_get_list_view(self):
+ """
+ GET requests to ListCreateAPIView should return list of objects.
+ """
+ request = factory.get('/basic/')
+ response = self.list_view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, self.data)
+
+ def test_get_detail_view(self):
+ """
+ GET requests to ListCreateAPIView should return list of objects.
+ """
+ request = factory.get('/basic/1')
+ response = self.detail_view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, self.data[0])
+
+
+class TestManyToManyHyperlinkedView(TestCase):
+ urls = 'rest_framework.tests.hyperlinkedserializers'
+
+ def setUp(self):
+ """
+ Create 3 BasicModel intances.
+ """
+ items = ['foo', 'bar', 'baz']
+ anchors = []
+ for item in items:
+ anchor = Anchor(text=item)
+ anchor.save()
+ anchors.append(anchor)
+
+ manytomany = ManyToManyModel()
+ manytomany.save()
+ manytomany.rel.add(*anchors)
+
+ self.data = [{
+ 'url': 'http://testserver/manytomany/1/',
+ 'rel': [
+ 'http://testserver/anchor/1/',
+ 'http://testserver/anchor/2/',
+ 'http://testserver/anchor/3/',
+ ]
+ }]
+ self.list_view = ManyToManyList.as_view()
+ self.detail_view = ManyToManyDetail.as_view()
+
+ def test_get_list_view(self):
+ """
+ GET requests to ListCreateAPIView should return list of objects.
+ """
+ request = factory.get('/manytomany/')
+ response = self.list_view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, self.data)
+
+ def test_get_detail_view(self):
+ """
+ GET requests to ListCreateAPIView should return list of objects.
+ """
+ request = factory.get('/manytomany/1/')
+ response = self.detail_view(request, pk=1).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data, self.data[0])
+
+
+class TestCreateWithForeignKeys(TestCase):
+ urls = 'rest_framework.tests.hyperlinkedserializers'
+
+ def setUp(self):
+ """
+ Create a blog post
+ """
+ self.post = BlogPost.objects.create(title="Test post")
+ self.create_view = BlogPostCommentListCreate.as_view()
+
+ def test_create_comment(self):
+
+ data = {
+ 'text': 'A test comment',
+ 'blog_post_url': 'http://testserver/posts/1/'
+ }
+
+ request = factory.post('/comments/', data=data)
+ response = self.create_view(request).render()
+ self.assertEqual(response.status_code, 201)
+ self.assertEqual(self.post.blogpostcomment_set.count(), 1)
+ self.assertEqual(self.post.blogpostcomment_set.all()[0].text, 'A test comment')
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
new file mode 100644
index 00000000..415e4d06
--- /dev/null
+++ b/rest_framework/tests/models.py
@@ -0,0 +1,128 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation
+
+# from django.contrib.auth.models import Group
+
+
+# class CustomUser(models.Model):
+# """
+# A custom user model, which uses a 'through' table for the foreign key
+# """
+# username = models.CharField(max_length=255, unique=True)
+# groups = models.ManyToManyField(
+# to=Group, blank=True, null=True, through='UserGroupMap'
+# )
+
+# @models.permalink
+# def get_absolute_url(self):
+# return ('custom_user', (), {
+# 'pk': self.id
+# })
+
+
+# class UserGroupMap(models.Model):
+# user = models.ForeignKey(to=CustomUser)
+# group = models.ForeignKey(to=Group)
+
+# @models.permalink
+# def get_absolute_url(self):
+# return ('user_group_map', (), {
+# 'pk': self.id
+# })
+
+def foobar():
+ return 'foobar'
+
+
+class RESTFrameworkModel(models.Model):
+ """
+ Base for test models that sets app_label, so they play nicely.
+ """
+ class Meta:
+ app_label = 'tests'
+ abstract = True
+
+
+class Anchor(RESTFrameworkModel):
+ text = models.CharField(max_length=100, default='anchor')
+
+
+class BasicModel(RESTFrameworkModel):
+ text = models.CharField(max_length=100)
+
+
+class SlugBasedModel(RESTFrameworkModel):
+ text = models.CharField(max_length=100)
+ slug = models.SlugField(max_length=32)
+
+
+class DefaultValueModel(RESTFrameworkModel):
+ text = models.CharField(default='foobar', max_length=100)
+
+
+class CallableDefaultValueModel(RESTFrameworkModel):
+ text = models.CharField(default=foobar, max_length=100)
+
+
+class ManyToManyModel(RESTFrameworkModel):
+ rel = models.ManyToManyField(Anchor)
+
+
+class ReadOnlyManyToManyModel(RESTFrameworkModel):
+ text = models.CharField(max_length=100, default='anchor')
+ rel = models.ManyToManyField(Anchor)
+
+# Models to test generic relations
+
+
+class Tag(RESTFrameworkModel):
+ tag_name = models.SlugField()
+
+
+class TaggedItem(RESTFrameworkModel):
+ tag = models.ForeignKey(Tag, related_name='items')
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = GenericForeignKey('content_type', 'object_id')
+
+ def __unicode__(self):
+ return self.tag.tag_name
+
+
+class Bookmark(RESTFrameworkModel):
+ url = models.URLField()
+ tags = GenericRelation(TaggedItem)
+
+
+# Model for regression test for #285
+
+class Comment(RESTFrameworkModel):
+ email = models.EmailField()
+ content = models.CharField(max_length=200)
+ created = models.DateTimeField(auto_now_add=True)
+
+
+class ActionItem(RESTFrameworkModel):
+ title = models.CharField(max_length=200)
+ done = models.BooleanField(default=False)
+
+
+# Models for reverse relations
+class BlogPost(RESTFrameworkModel):
+ title = models.CharField(max_length=100)
+
+
+class BlogPostComment(RESTFrameworkModel):
+ text = models.TextField()
+ blog_post = models.ForeignKey(BlogPost)
+
+
+class Person(RESTFrameworkModel):
+ name = models.CharField(max_length=10)
+ age = models.IntegerField(null=True, blank=True)
+
+
+# Model for issue #324
+class BlankFieldModel(RESTFrameworkModel):
+ title = models.CharField(max_length=100, blank=True)
diff --git a/rest_framework/tests/modelviews.py b/rest_framework/tests/modelviews.py
new file mode 100644
index 00000000..1f8468e8
--- /dev/null
+++ b/rest_framework/tests/modelviews.py
@@ -0,0 +1,90 @@
+# from django.conf.urls.defaults import patterns, url
+# from django.forms import ModelForm
+# from django.contrib.auth.models import Group, User
+# from rest_framework.resources import ModelResource
+# from rest_framework.views import ListOrCreateModelView, InstanceModelView
+# from rest_framework.tests.models import CustomUser
+# from rest_framework.tests.testcases import TestModelsTestCase
+
+
+# class GroupResource(ModelResource):
+# model = Group
+
+
+# class UserForm(ModelForm):
+# class Meta:
+# model = User
+# exclude = ('last_login', 'date_joined')
+
+
+# class UserResource(ModelResource):
+# model = User
+# form = UserForm
+
+
+# class CustomUserResource(ModelResource):
+# model = CustomUser
+
+# urlpatterns = patterns('',
+# url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
+# url(r'^users/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)),
+# url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'),
+# url(r'^customusers/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)),
+# url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'),
+# url(r'^groups/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)),
+# )
+
+
+# class ModelViewTests(TestModelsTestCase):
+# """Test the model views rest_framework provides"""
+# urls = 'rest_framework.tests.modelviews'
+
+# def test_creation(self):
+# """Ensure that a model object can be created"""
+# self.assertEqual(0, Group.objects.count())
+
+# response = self.client.post('/groups/', {'name': 'foo'})
+
+# self.assertEqual(response.status_code, 201)
+# self.assertEqual(1, Group.objects.count())
+# self.assertEqual('foo', Group.objects.all()[0].name)
+
+# def test_creation_with_m2m_relation(self):
+# """Ensure that a model object with a m2m relation can be created"""
+# group = Group(name='foo')
+# group.save()
+# self.assertEqual(0, User.objects.count())
+
+# response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
+
+# self.assertEqual(response.status_code, 201)
+# self.assertEqual(1, User.objects.count())
+
+# user = User.objects.all()[0]
+# self.assertEqual('bar', user.username)
+# self.assertEqual('baz', user.password)
+# self.assertEqual(1, user.groups.count())
+
+# group = user.groups.all()[0]
+# self.assertEqual('foo', group.name)
+
+# def test_creation_with_m2m_relation_through(self):
+# """
+# Ensure that a model object with a m2m relation can be created where that
+# relation uses a through table
+# """
+# group = Group(name='foo')
+# group.save()
+# self.assertEqual(0, User.objects.count())
+
+# response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
+
+# self.assertEqual(response.status_code, 201)
+# self.assertEqual(1, CustomUser.objects.count())
+
+# user = CustomUser.objects.all()[0]
+# self.assertEqual('bar', user.username)
+# self.assertEqual(1, user.groups.count())
+
+# group = user.groups.all()[0]
+# self.assertEqual('foo', group.name)
diff --git a/rest_framework/tests/negotiation.py b/rest_framework/tests/negotiation.py
new file mode 100644
index 00000000..e06354ea
--- /dev/null
+++ b/rest_framework/tests/negotiation.py
@@ -0,0 +1,37 @@
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework.negotiation import DefaultContentNegotiation
+
+factory = RequestFactory()
+
+
+class MockJSONRenderer(object):
+ media_type = 'application/json'
+
+
+class MockHTMLRenderer(object):
+ media_type = 'text/html'
+
+
+class TestAcceptedMediaType(TestCase):
+ def setUp(self):
+ self.renderers = [MockJSONRenderer(), MockHTMLRenderer()]
+ self.negotiator = DefaultContentNegotiation()
+
+ def select_renderer(self, request):
+ return self.negotiator.select_renderer(request, self.renderers)
+
+ def test_client_without_accept_use_renderer(self):
+ request = factory.get('/')
+ accepted_renderer, accepted_media_type = self.select_renderer(request)
+ self.assertEquals(accepted_media_type, 'application/json')
+
+ def test_client_underspecifies_accept_use_renderer(self):
+ request = factory.get('/', HTTP_ACCEPT='*/*')
+ accepted_renderer, accepted_media_type = self.select_renderer(request)
+ self.assertEquals(accepted_media_type, 'application/json')
+
+ def test_client_overspecifies_accept_use_client(self):
+ request = factory.get('/', HTTP_ACCEPT='application/json; indent=8')
+ accepted_renderer, accepted_media_type = self.select_renderer(request)
+ self.assertEquals(accepted_media_type, 'application/json; indent=8')
diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py
new file mode 100644
index 00000000..a939c9ef
--- /dev/null
+++ b/rest_framework/tests/pagination.py
@@ -0,0 +1,87 @@
+from django.core.paginator import Paginator
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework import generics, status, pagination
+from rest_framework.tests.models import BasicModel
+
+factory = RequestFactory()
+
+
+class RootView(generics.ListCreateAPIView):
+ """
+ Example description for OPTIONS.
+ """
+ model = BasicModel
+ paginate_by = 10
+
+
+class IntegrationTestPagination(TestCase):
+ """
+ Integration tests for paginated list views.
+ """
+
+ def setUp(self):
+ """
+ Create 26 BasicModel intances.
+ """
+ for char in 'abcdefghijklmnopqrstuvwxyz':
+ BasicModel(text=char * 3).save()
+ self.objects = BasicModel.objects
+ self.data = [
+ {'id': obj.id, 'text': obj.text}
+ for obj in self.objects.all()
+ ]
+ self.view = RootView.as_view()
+
+ def test_get_paginated_root_view(self):
+ """
+ GET requests to paginated ListCreateAPIView should return paginated results.
+ """
+ request = factory.get('/')
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data['count'], 26)
+ self.assertEquals(response.data['results'], self.data[:10])
+ self.assertNotEquals(response.data['next'], None)
+ self.assertEquals(response.data['previous'], None)
+
+ request = factory.get(response.data['next'])
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data['count'], 26)
+ self.assertEquals(response.data['results'], self.data[10:20])
+ self.assertNotEquals(response.data['next'], None)
+ self.assertNotEquals(response.data['previous'], None)
+
+ request = factory.get(response.data['next'])
+ response = self.view(request).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)
+ self.assertEquals(response.data['count'], 26)
+ self.assertEquals(response.data['results'], self.data[20:])
+ self.assertEquals(response.data['next'], None)
+ self.assertNotEquals(response.data['previous'], None)
+
+
+class UnitTestPagination(TestCase):
+ """
+ Unit tests for pagination of primative objects.
+ """
+
+ def setUp(self):
+ self.objects = [char * 3 for char in 'abcdefghijklmnopqrstuvwxyz']
+ paginator = Paginator(self.objects, 10)
+ self.first_page = paginator.page(1)
+ self.last_page = paginator.page(3)
+
+ def test_native_pagination(self):
+ serializer = pagination.PaginationSerializer(instance=self.first_page)
+ self.assertEquals(serializer.data['count'], 26)
+ self.assertEquals(serializer.data['next'], '?page=2')
+ self.assertEquals(serializer.data['previous'], None)
+ self.assertEquals(serializer.data['results'], self.objects[:10])
+
+ serializer = pagination.PaginationSerializer(instance=self.last_page)
+ self.assertEquals(serializer.data['count'], 26)
+ self.assertEquals(serializer.data['next'], None)
+ self.assertEquals(serializer.data['previous'], '?page=2')
+ self.assertEquals(serializer.data['results'], self.objects[20:])
diff --git a/djangorestframework/tests/parsers.py b/rest_framework/tests/parsers.py
index 1ecc1760..8ab8a52f 100644
--- a/djangorestframework/tests/parsers.py
+++ b/rest_framework/tests/parsers.py
@@ -1,8 +1,8 @@
# """
# ..
-# >>> from djangorestframework.parsers import FormParser
-# >>> from djangorestframework.compat import RequestFactory
-# >>> from djangorestframework.views import View
+# >>> from rest_framework.parsers import FormParser
+# >>> from django.test.client import RequestFactory
+# >>> from rest_framework.views import View
# >>> from StringIO import StringIO
# >>> from urllib import urlencode
# >>> req = RequestFactory().get('/')
@@ -83,9 +83,9 @@
# import httplib, mimetypes
# from tempfile import TemporaryFile
# from django.test import TestCase
-# from djangorestframework.compat import RequestFactory
-# from djangorestframework.parsers import MultiPartParser
-# from djangorestframework.views import View
+# from django.test.client import RequestFactory
+# from rest_framework.parsers import MultiPartParser
+# from rest_framework.views import View
# from StringIO import StringIO
#
# def encode_multipart_formdata(fields, files):
@@ -132,30 +132,32 @@
# self.assertEqual(files['file1'].read(), 'blablabla')
from StringIO import StringIO
-from cgi import parse_qs
from django import forms
from django.test import TestCase
-from djangorestframework.parsers import FormParser
-from djangorestframework.parsers import XMLParser
+from rest_framework.parsers import FormParser
+from rest_framework.parsers import XMLParser
import datetime
+
class Form(forms.Form):
field1 = forms.CharField(max_length=3)
field2 = forms.CharField()
+
class TestFormParser(TestCase):
def setUp(self):
self.string = "field1=abc&field2=defghijk"
def test_parse(self):
""" Make sure the `QueryDict` works OK """
- parser = FormParser(None)
+ parser = FormParser()
stream = StringIO(self.string)
- (data, files) = parser.parse(stream)
+ data = parser.parse(stream)
self.assertEqual(Form(data).is_valid(), True)
+
class TestXMLParser(TestCase):
def setUp(self):
self._input = StringIO(
@@ -163,13 +165,13 @@ class TestXMLParser(TestCase):
'<root>'
'<field_a>121.0</field_a>'
'<field_b>dasd</field_b>'
- '<field_c></field_c>'
+ '<field_c></field_c>'
'<field_d>2011-12-25 12:45:00</field_d>'
'</root>'
- )
- self._data = {
+ )
+ self._data = {
'field_a': 121,
- 'field_b': 'dasd',
+ 'field_b': 'dasd',
'field_c': None,
'field_d': datetime.datetime(2011, 12, 25, 12, 45, 00)
}
@@ -183,28 +185,28 @@ class TestXMLParser(TestCase):
'</sub_data_list>'
'<name>name</name>'
'</root>'
- )
+ )
self._complex_data = {
- "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
- "name": "name",
+ "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
+ "name": "name",
"sub_data_list": [
{
- "sub_id": 1,
+ "sub_id": 1,
"sub_name": "first"
- },
+ },
{
- "sub_id": 2,
+ "sub_id": 2,
"sub_name": "second"
}
]
- }
+ }
def test_parse(self):
- parser = XMLParser(None)
- (data, files) = parser.parse(self._input)
+ parser = XMLParser()
+ data = parser.parse(self._input)
self.assertEqual(data, self._data)
def test_complex_data_parse(self):
- parser = XMLParser(None)
- (data, files) = parser.parse(self._complex_data_input)
+ parser = XMLParser()
+ data = parser.parse(self._complex_data_input)
self.assertEqual(data, self._complex_data)
diff --git a/djangorestframework/tests/renderers.py b/rest_framework/tests/renderers.py
index 3ed5ab28..48d8d9bd 100644
--- a/djangorestframework/tests/renderers.py
+++ b/rest_framework/tests/renderers.py
@@ -2,20 +2,22 @@ import re
from django.conf.urls.defaults import patterns, url, include
from django.test import TestCase
+from django.test.client import RequestFactory
-from djangorestframework import status
-from djangorestframework.views import View
-from djangorestframework.compat import View as DjangoView
-from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
- XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
-from djangorestframework.parsers import JSONParser, YAMLParser, XMLParser
-from djangorestframework.mixins import ResponseMixin
-from djangorestframework.response import Response
+from rest_framework import status, permissions
+from rest_framework.compat import yaml
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
+ XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
+from rest_framework.parsers import YAMLParser, XMLParser
+from rest_framework.settings import api_settings
from StringIO import StringIO
import datetime
from decimal import Decimal
+
DUMMYSTATUS = status.HTTP_200_OK
DUMMYCONTENT = 'dummycontent'
@@ -23,66 +25,106 @@ RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
+expected_results = [
+ ((elem for elem in [1, 2, 3]), JSONRenderer, '[1, 2, 3]') # Generator
+]
+
+
+class BasicRendererTests(TestCase):
+ def test_expected_results(self):
+ for value, renderer_cls, expected in expected_results:
+ output = renderer_cls().render(value)
+ self.assertEquals(output, expected)
+
+
class RendererA(BaseRenderer):
media_type = 'mock/renderera'
format = "formata"
- def render(self, obj=None, media_type=None):
- return RENDERER_A_SERIALIZER(obj)
+ def render(self, data, media_type=None, renderer_context=None):
+ return RENDERER_A_SERIALIZER(data)
class RendererB(BaseRenderer):
media_type = 'mock/rendererb'
format = "formatb"
- def render(self, obj=None, media_type=None):
- return RENDERER_B_SERIALIZER(obj)
+ def render(self, data, media_type=None, renderer_context=None):
+ return RENDERER_B_SERIALIZER(data)
-class MockView(ResponseMixin, DjangoView):
- renderers = (RendererA, RendererB)
+class MockView(APIView):
+ renderer_classes = (RendererA, RendererB)
def get(self, request, **kwargs):
- response = Response(DUMMYSTATUS, DUMMYCONTENT)
- return self.render(response)
+ response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
+ return response
-class MockGETView(View):
+class MockGETView(APIView):
def get(self, request, **kwargs):
- return {'foo': ['bar', 'baz']}
+ return Response({'foo': ['bar', 'baz']})
-class HTMLView(View):
- renderers = (DocumentingHTMLRenderer, )
+class HTMLView(APIView):
+ renderer_classes = (BrowsableAPIRenderer, )
def get(self, request, **kwargs):
- return 'text'
+ return Response('text')
-class HTMLView1(View):
- renderers = (DocumentingHTMLRenderer, JSONRenderer)
+class HTMLView1(APIView):
+ renderer_classes = (BrowsableAPIRenderer, JSONRenderer)
def get(self, request, **kwargs):
- return 'text'
+ return Response('text')
urlpatterns = patterns('',
- url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
- url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
- url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
- url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
+ url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
+ url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
+ url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])),
+ url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
url(r'^html$', HTMLView.as_view()),
url(r'^html1$', HTMLView1.as_view()),
- url(r'^api', include('djangorestframework.urls', namespace='djangorestframework'))
+ url(r'^api', include('rest_framework.urls', namespace='rest_framework'))
)
-class RendererIntegrationTests(TestCase):
+class POSTDeniedPermission(permissions.BasePermission):
+ def has_permission(self, request, view, obj=None):
+ return request.method != 'POST'
+
+
+class POSTDeniedView(APIView):
+ renderer_classes = (BrowsableAPIRenderer,)
+ permission_classes = (POSTDeniedPermission,)
+
+ def get(self, request):
+ return Response()
+
+ def post(self, request):
+ return Response()
+
+ def put(self, request):
+ return Response()
+
+
+class DocumentingRendererTests(TestCase):
+ def test_only_permitted_forms_are_displayed(self):
+ view = POSTDeniedView.as_view()
+ request = RequestFactory().get('/')
+ response = view(request).render()
+ self.assertNotContains(response, '>POST<')
+ self.assertContains(response, '>PUT<')
+
+
+class RendererEndToEndTests(TestCase):
"""
End-to-end testing of renderers using an RendererMixin on a generic view.
"""
- urls = 'djangorestframework.tests.renderers'
+ urls = 'rest_framework.tests.renderers'
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
@@ -123,7 +165,11 @@ class RendererIntegrationTests(TestCase):
def test_specified_renderer_serializes_content_on_accept_query(self):
"""The '_accept' query string should behave in the same way as the Accept header."""
- resp = self.client.get('/?_accept=%s' % RendererB.media_type)
+ param = '?%s=%s' % (
+ api_settings.URL_ACCEPT_OVERRIDE,
+ RendererB.media_type
+ )
+ resp = self.client.get('/' + param)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
@@ -136,7 +182,11 @@ class RendererIntegrationTests(TestCase):
def test_specified_renderer_serializes_content_on_format_query(self):
"""If a 'format' query is specified, the renderer with the matching
format attribute should serialize the response."""
- resp = self.client.get('/?format=%s' % RendererB.format)
+ param = '?%s=%s' % (
+ api_settings.URL_FORMAT_OVERRIDE,
+ RendererB.format
+ )
+ resp = self.client.get('/' + param)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
@@ -152,27 +202,16 @@ class RendererIntegrationTests(TestCase):
def test_specified_renderer_is_used_on_format_query_with_matching_accept(self):
"""If both a 'format' query and a matching Accept header specified,
the renderer with the matching format attribute should serialize the response."""
- resp = self.client.get('/?format=%s' % RendererB.format,
+ param = '?%s=%s' % (
+ api_settings.URL_FORMAT_OVERRIDE,
+ RendererB.format
+ )
+ resp = self.client.get('/' + param,
HTTP_ACCEPT=RendererB.media_type)
self.assertEquals(resp['Content-Type'], RendererB.media_type)
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
- def test_conflicting_format_query_and_accept_ignores_accept(self):
- """If a 'format' query is specified that does not match the Accept
- header, we should only honor the 'format' query string."""
- resp = self.client.get('/?format=%s' % RendererB.format,
- HTTP_ACCEPT='dummy')
- self.assertEquals(resp['Content-Type'], RendererB.media_type)
- self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
- self.assertEquals(resp.status_code, DUMMYSTATUS)
-
- def test_bla(self):
- resp = self.client.get('/?format=formatb',
- HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
- self.assertEquals(resp['Content-Type'], RendererB.media_type)
- self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
- self.assertEquals(resp.status_code, DUMMYSTATUS)
_flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
@@ -185,6 +224,7 @@ def strip_trailing_whitespace(content):
"""
return re.sub(' +\n', '\n', content)
+
class JSONRendererTests(TestCase):
"""
Tests specific to the JSON Renderer
@@ -195,7 +235,7 @@ class JSONRendererTests(TestCase):
Test basic JSON rendering.
"""
obj = {'foo': ['bar', 'baz']}
- renderer = JSONRenderer(None)
+ renderer = JSONRenderer()
content = renderer.render(obj, 'application/json')
# Fix failing test case which depends on version of JSON library.
self.assertEquals(content, _flat_repr)
@@ -205,40 +245,26 @@ class JSONRendererTests(TestCase):
Test JSON rendering with additional content type arguments supplied.
"""
obj = {'foo': ['bar', 'baz']}
- renderer = JSONRenderer(None)
+ renderer = JSONRenderer()
content = renderer.render(obj, 'application/json; indent=2')
self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
- def test_render_and_parse(self):
- """
- Test rendering and then parsing returns the original object.
- IE obj -> render -> parse -> obj.
- """
- obj = {'foo': ['bar', 'baz']}
-
- renderer = JSONRenderer(None)
- parser = JSONParser(None)
-
- content = renderer.render(obj, 'application/json')
- (data, files) = parser.parse(StringIO(content))
- self.assertEquals(obj, data)
-
class JSONPRendererTests(TestCase):
"""
Tests specific to the JSONP Renderer
"""
- urls = 'djangorestframework.tests.renderers'
+ urls = 'rest_framework.tests.renderers'
def test_without_callback_with_json_renderer(self):
"""
Test JSONP rendering with View JSON Renderer.
"""
resp = self.client.get('/jsonp/jsonrenderer',
- HTTP_ACCEPT='application/json-p')
+ HTTP_ACCEPT='application/javascript')
self.assertEquals(resp.status_code, 200)
- self.assertEquals(resp['Content-Type'], 'application/json-p')
+ self.assertEquals(resp['Content-Type'], 'application/javascript')
self.assertEquals(resp.content, 'callback(%s);' % _flat_repr)
def test_without_callback_without_json_renderer(self):
@@ -246,9 +272,9 @@ class JSONPRendererTests(TestCase):
Test JSONP rendering without View JSON Renderer.
"""
resp = self.client.get('/jsonp/nojsonrenderer',
- HTTP_ACCEPT='application/json-p')
+ HTTP_ACCEPT='application/javascript')
self.assertEquals(resp.status_code, 200)
- self.assertEquals(resp['Content-Type'], 'application/json-p')
+ self.assertEquals(resp['Content-Type'], 'application/javascript')
self.assertEquals(resp.content, 'callback(%s);' % _flat_repr)
def test_with_callback(self):
@@ -257,13 +283,13 @@ class JSONPRendererTests(TestCase):
"""
callback_func = 'myjsonpcallback'
resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func,
- HTTP_ACCEPT='application/json-p')
+ HTTP_ACCEPT='application/javascript')
self.assertEquals(resp.status_code, 200)
- self.assertEquals(resp['Content-Type'], 'application/json-p')
+ self.assertEquals(resp['Content-Type'], 'application/javascript')
self.assertEquals(resp.content, '%s(%s);' % (callback_func, _flat_repr))
-if YAMLRenderer:
+if yaml:
_yaml_repr = 'foo: [bar, baz]\n'
class YAMLRendererTests(TestCase):
@@ -276,7 +302,7 @@ if YAMLRenderer:
Test basic YAML rendering.
"""
obj = {'foo': ['bar', 'baz']}
- renderer = YAMLRenderer(None)
+ renderer = YAMLRenderer()
content = renderer.render(obj, 'application/yaml')
self.assertEquals(content, _yaml_repr)
@@ -287,30 +313,29 @@ if YAMLRenderer:
"""
obj = {'foo': ['bar', 'baz']}
- renderer = YAMLRenderer(None)
- parser = YAMLParser(None)
+ renderer = YAMLRenderer()
+ parser = YAMLParser()
content = renderer.render(obj, 'application/yaml')
- (data, files) = parser.parse(StringIO(content))
+ data = parser.parse(StringIO(content))
self.assertEquals(obj, data)
-
class XMLRendererTestCase(TestCase):
"""
Tests specific to the XML Renderer
"""
_complex_data = {
- "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
- "name": "name",
+ "creation_date": datetime.datetime(2011, 12, 25, 12, 45, 00),
+ "name": "name",
"sub_data_list": [
{
- "sub_id": 1,
+ "sub_id": 1,
"sub_name": "first"
- },
+ },
{
- "sub_id": 2,
+ "sub_id": 2,
"sub_name": "second"
}
]
@@ -320,7 +345,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render({'field': 'astring'}, 'application/xml')
self.assertXMLContains(content, '<field>astring</field>')
@@ -328,7 +353,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render({'field': 111}, 'application/xml')
self.assertXMLContains(content, '<field>111</field>')
@@ -336,7 +361,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render({
'field': datetime.datetime(2011, 12, 25, 12, 45, 00)
}, 'application/xml')
@@ -346,7 +371,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render({'field': 123.4}, 'application/xml')
self.assertXMLContains(content, '<field>123.4</field>')
@@ -354,7 +379,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render({'field': Decimal('111.2')}, 'application/xml')
self.assertXMLContains(content, '<field>111.2</field>')
@@ -362,15 +387,15 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render({'field': None}, 'application/xml')
self.assertXMLContains(content, '<field></field>')
-
+
def test_render_complex_data(self):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = renderer.render(self._complex_data, 'application/xml')
self.assertXMLContains(content, '<sub_name>first</sub_name>')
self.assertXMLContains(content, '<sub_name>second</sub_name>')
@@ -379,11 +404,11 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
- renderer = XMLRenderer(None)
+ renderer = XMLRenderer()
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
-
- parser = XMLParser(None)
- complex_data_out, dummy = parser.parse(content)
+
+ parser = XMLParser()
+ complex_data_out = parser.parse(content)
error_msg = "complex data differs!IN:\n %s \n\n OUT:\n %s" % (repr(self._complex_data), repr(complex_data_out))
self.assertEqual(self._complex_data, complex_data_out, error_msg)
@@ -391,22 +416,3 @@ class XMLRendererTestCase(TestCase):
self.assertTrue(xml.startswith('<?xml version="1.0" encoding="utf-8"?>\n<root>'))
self.assertTrue(xml.endswith('</root>'))
self.assertTrue(string in xml, '%r not in %r' % (string, xml))
-
-
-class Issue122Tests(TestCase):
- """
- Tests that covers #122.
- """
- urls = 'djangorestframework.tests.renderers'
-
- def test_only_html_renderer(self):
- """
- Test if no infinite recursion occurs.
- """
- resp = self.client.get('/html')
-
- def test_html_renderer_is_first(self):
- """
- Test if no infinite recursion occurs.
- """
- resp = self.client.get('/html1')
diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py
new file mode 100644
index 00000000..ff48f3fa
--- /dev/null
+++ b/rest_framework/tests/request.py
@@ -0,0 +1,278 @@
+"""
+Tests for content parsing, and form-overloaded content parsing.
+"""
+from django.conf.urls.defaults import patterns
+from django.contrib.auth.models import User
+from django.test import TestCase, Client
+from django.utils import simplejson as json
+
+from rest_framework import status
+from rest_framework.authentication import SessionAuthentication
+from django.test.client import RequestFactory
+from rest_framework.parsers import (
+ BaseParser,
+ FormParser,
+ MultiPartParser,
+ JSONParser
+)
+from rest_framework.request import Request
+from rest_framework.response import Response
+from rest_framework.settings import api_settings
+from rest_framework.views import APIView
+
+
+factory = RequestFactory()
+
+
+class PlainTextParser(BaseParser):
+ media_type = 'text/plain'
+
+ def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Returns a 2-tuple of `(data, files)`.
+
+ `data` will simply be a string representing the body of the request.
+ `files` will always be `None`.
+ """
+ return stream.read()
+
+
+class TestMethodOverloading(TestCase):
+ def test_method(self):
+ """
+ Request methods should be same as underlying request.
+ """
+ request = Request(factory.get('/'))
+ self.assertEqual(request.method, 'GET')
+ request = Request(factory.post('/'))
+ self.assertEqual(request.method, 'POST')
+
+ def test_overloaded_method(self):
+ """
+ POST requests can be overloaded to another method by setting a
+ reserved form field
+ """
+ request = Request(factory.post('/', {api_settings.FORM_METHOD_OVERRIDE: 'DELETE'}))
+ self.assertEqual(request.method, 'DELETE')
+
+
+class TestContentParsing(TestCase):
+ def test_standard_behaviour_determines_no_content_GET(self):
+ """
+ Ensure request.DATA returns None for GET request with no content.
+ """
+ request = Request(factory.get('/'))
+ self.assertEqual(request.DATA, None)
+
+ def test_standard_behaviour_determines_no_content_HEAD(self):
+ """
+ Ensure request.DATA returns None for HEAD request.
+ """
+ request = Request(factory.head('/'))
+ self.assertEqual(request.DATA, None)
+
+ def test_request_DATA_with_form_content(self):
+ """
+ Ensure request.DATA returns content for POST request with form content.
+ """
+ data = {'qwerty': 'uiop'}
+ request = Request(factory.post('/', data))
+ request.parsers = (FormParser(), MultiPartParser())
+ self.assertEqual(request.DATA.items(), data.items())
+
+ def test_request_DATA_with_text_content(self):
+ """
+ Ensure request.DATA returns content for POST request with
+ non-form content.
+ """
+ content = 'qwerty'
+ content_type = 'text/plain'
+ request = Request(factory.post('/', content, content_type=content_type))
+ request.parsers = (PlainTextParser(),)
+ self.assertEqual(request.DATA, content)
+
+ def test_request_POST_with_form_content(self):
+ """
+ Ensure request.POST returns content for POST request with form content.
+ """
+ data = {'qwerty': 'uiop'}
+ request = Request(factory.post('/', data))
+ request.parsers = (FormParser(), MultiPartParser())
+ self.assertEqual(request.POST.items(), data.items())
+
+ def test_standard_behaviour_determines_form_content_PUT(self):
+ """
+ Ensure request.DATA returns content for PUT request with form content.
+ """
+ data = {'qwerty': 'uiop'}
+
+ from django import VERSION
+
+ if VERSION >= (1, 5):
+ from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart
+ request = Request(factory.put('/', encode_multipart(BOUNDARY, data),
+ content_type=MULTIPART_CONTENT))
+ else:
+ request = Request(factory.put('/', data))
+
+ request.parsers = (FormParser(), MultiPartParser())
+ self.assertEqual(request.DATA.items(), data.items())
+
+ def test_standard_behaviour_determines_non_form_content_PUT(self):
+ """
+ Ensure request.DATA returns content for PUT request with
+ non-form content.
+ """
+ content = 'qwerty'
+ content_type = 'text/plain'
+ request = Request(factory.put('/', content, content_type=content_type))
+ request.parsers = (PlainTextParser(), )
+ self.assertEqual(request.DATA, content)
+
+ def test_overloaded_behaviour_allows_content_tunnelling(self):
+ """
+ Ensure request.DATA returns content for overloaded POST request.
+ """
+ json_data = {'foobar': 'qwerty'}
+ content = json.dumps(json_data)
+ content_type = 'application/json'
+ form_data = {
+ api_settings.FORM_CONTENT_OVERRIDE: content,
+ api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
+ }
+ request = Request(factory.post('/', form_data))
+ request.parsers = (JSONParser(), )
+ self.assertEqual(request.DATA, json_data)
+
+ # def test_accessing_post_after_data_form(self):
+ # """
+ # Ensures request.POST can be accessed after request.DATA in
+ # form request.
+ # """
+ # data = {'qwerty': 'uiop'}
+ # request = factory.post('/', data=data)
+ # self.assertEqual(request.DATA.items(), data.items())
+ # self.assertEqual(request.POST.items(), data.items())
+
+ # def test_accessing_post_after_data_for_json(self):
+ # """
+ # Ensures request.POST can be accessed after request.DATA in
+ # json request.
+ # """
+ # data = {'qwerty': 'uiop'}
+ # content = json.dumps(data)
+ # content_type = 'application/json'
+ # parsers = (JSONParser, )
+
+ # request = factory.post('/', content, content_type=content_type,
+ # parsers=parsers)
+ # self.assertEqual(request.DATA.items(), data.items())
+ # self.assertEqual(request.POST.items(), [])
+
+ # def test_accessing_post_after_data_for_overloaded_json(self):
+ # """
+ # Ensures request.POST can be accessed after request.DATA in overloaded
+ # json request.
+ # """
+ # data = {'qwerty': 'uiop'}
+ # content = json.dumps(data)
+ # content_type = 'application/json'
+ # parsers = (JSONParser, )
+ # form_data = {Request._CONTENT_PARAM: content,
+ # Request._CONTENTTYPE_PARAM: content_type}
+
+ # request = factory.post('/', form_data, parsers=parsers)
+ # self.assertEqual(request.DATA.items(), data.items())
+ # self.assertEqual(request.POST.items(), form_data.items())
+
+ # def test_accessing_data_after_post_form(self):
+ # """
+ # Ensures request.DATA can be accessed after request.POST in
+ # form request.
+ # """
+ # data = {'qwerty': 'uiop'}
+ # parsers = (FormParser, MultiPartParser)
+ # request = factory.post('/', data, parsers=parsers)
+
+ # self.assertEqual(request.POST.items(), data.items())
+ # self.assertEqual(request.DATA.items(), data.items())
+
+ # def test_accessing_data_after_post_for_json(self):
+ # """
+ # Ensures request.DATA can be accessed after request.POST in
+ # json request.
+ # """
+ # data = {'qwerty': 'uiop'}
+ # content = json.dumps(data)
+ # content_type = 'application/json'
+ # parsers = (JSONParser, )
+ # request = factory.post('/', content, content_type=content_type,
+ # parsers=parsers)
+ # self.assertEqual(request.POST.items(), [])
+ # self.assertEqual(request.DATA.items(), data.items())
+
+ # def test_accessing_data_after_post_for_overloaded_json(self):
+ # """
+ # Ensures request.DATA can be accessed after request.POST in overloaded
+ # json request
+ # """
+ # data = {'qwerty': 'uiop'}
+ # content = json.dumps(data)
+ # content_type = 'application/json'
+ # parsers = (JSONParser, )
+ # form_data = {Request._CONTENT_PARAM: content,
+ # Request._CONTENTTYPE_PARAM: content_type}
+
+ # request = factory.post('/', form_data, parsers=parsers)
+ # self.assertEqual(request.POST.items(), form_data.items())
+ # self.assertEqual(request.DATA.items(), data.items())
+
+
+class MockView(APIView):
+ authentication_classes = (SessionAuthentication,)
+
+ def post(self, request):
+ if request.POST.get('example') is not None:
+ return Response(status=status.HTTP_200_OK)
+
+ return Response(status=status.INTERNAL_SERVER_ERROR)
+
+urlpatterns = patterns('',
+ (r'^$', MockView.as_view()),
+)
+
+
+class TestContentParsingWithAuthentication(TestCase):
+ urls = 'rest_framework.tests.request'
+
+ def setUp(self):
+ self.csrf_client = Client(enforce_csrf_checks=True)
+ self.username = 'john'
+ self.email = 'lennon@thebeatles.com'
+ self.password = 'password'
+ self.user = User.objects.create_user(self.username, self.email, self.password)
+
+ def test_user_logged_in_authentication_has_POST_when_not_logged_in(self):
+ """
+ Ensures request.POST exists after SessionAuthentication when user
+ doesn't log in.
+ """
+ content = {'example': 'example'}
+
+ response = self.client.post('/', content)
+ self.assertEqual(status.HTTP_200_OK, response.status_code)
+
+ response = self.csrf_client.post('/', content)
+ self.assertEqual(status.HTTP_200_OK, response.status_code)
+
+ # def test_user_logged_in_authentication_has_post_when_logged_in(self):
+ # """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
+ # self.client.login(username='john', password='password')
+ # self.csrf_client.login(username='john', password='password')
+ # content = {'example': 'example'}
+
+ # response = self.client.post('/', content)
+ # self.assertEqual(status.OK, response.status_code, "POST data is malformed")
+
+ # response = self.csrf_client.post('/', content)
+ # self.assertEqual(status.OK, response.status_code, "POST data is malformed")
diff --git a/rest_framework/tests/response.py b/rest_framework/tests/response.py
new file mode 100644
index 00000000..18b6af39
--- /dev/null
+++ b/rest_framework/tests/response.py
@@ -0,0 +1,182 @@
+import unittest
+
+from django.conf.urls.defaults import patterns, url, include
+from django.test import TestCase
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from rest_framework import status
+from rest_framework.renderers import (
+ BaseRenderer,
+ JSONRenderer,
+ BrowsableAPIRenderer
+)
+from rest_framework.settings import api_settings
+
+
+class MockPickleRenderer(BaseRenderer):
+ media_type = 'application/pickle'
+
+
+class MockJsonRenderer(BaseRenderer):
+ media_type = 'application/json'
+
+
+DUMMYSTATUS = status.HTTP_200_OK
+DUMMYCONTENT = 'dummycontent'
+
+RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
+RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
+
+
+class RendererA(BaseRenderer):
+ media_type = 'mock/renderera'
+ format = "formata"
+
+ def render(self, data, media_type=None, renderer_context=None):
+ return RENDERER_A_SERIALIZER(data)
+
+
+class RendererB(BaseRenderer):
+ media_type = 'mock/rendererb'
+ format = "formatb"
+
+ def render(self, data, media_type=None, renderer_context=None):
+ return RENDERER_B_SERIALIZER(data)
+
+
+class MockView(APIView):
+ renderer_classes = (RendererA, RendererB)
+
+ def get(self, request, **kwargs):
+ return Response(DUMMYCONTENT, status=DUMMYSTATUS)
+
+
+class HTMLView(APIView):
+ renderer_classes = (BrowsableAPIRenderer, )
+
+ def get(self, request, **kwargs):
+ return Response('text')
+
+
+class HTMLView1(APIView):
+ renderer_classes = (BrowsableAPIRenderer, JSONRenderer)
+
+ def get(self, request, **kwargs):
+ return Response('text')
+
+
+urlpatterns = patterns('',
+ url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
+ url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
+ url(r'^html$', HTMLView.as_view()),
+ url(r'^html1$', HTMLView1.as_view()),
+ url(r'^restframework', include('rest_framework.urls', namespace='rest_framework'))
+)
+
+
+# TODO: Clean tests bellow - remove duplicates with above, better unit testing, ...
+class RendererIntegrationTests(TestCase):
+ """
+ End-to-end testing of renderers using an ResponseMixin on a generic view.
+ """
+
+ urls = 'rest_framework.tests.response'
+
+ def test_default_renderer_serializes_content(self):
+ """If the Accept header is not set the default renderer should serialize the response."""
+ resp = self.client.get('/')
+ self.assertEquals(resp['Content-Type'], RendererA.media_type)
+ self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_head_method_serializes_no_content(self):
+ """No response must be included in HEAD requests."""
+ resp = self.client.head('/')
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+ self.assertEquals(resp['Content-Type'], RendererA.media_type)
+ self.assertEquals(resp.content, '')
+
+ def test_default_renderer_serializes_content_on_accept_any(self):
+ """If the Accept header is set to */* the default renderer should serialize the response."""
+ resp = self.client.get('/', HTTP_ACCEPT='*/*')
+ self.assertEquals(resp['Content-Type'], RendererA.media_type)
+ self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_specified_renderer_serializes_content_default_case(self):
+ """If the Accept header is set the specified renderer should serialize the response.
+ (In this case we check that works for the default renderer)"""
+ resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
+ self.assertEquals(resp['Content-Type'], RendererA.media_type)
+ self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_specified_renderer_serializes_content_non_default_case(self):
+ """If the Accept header is set the specified renderer should serialize the response.
+ (In this case we check that works for a non-default renderer)"""
+ resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_specified_renderer_serializes_content_on_accept_query(self):
+ """The '_accept' query string should behave in the same way as the Accept header."""
+ param = '?%s=%s' % (
+ api_settings.URL_ACCEPT_OVERRIDE,
+ RendererB.media_type
+ )
+ resp = self.client.get('/' + param)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ @unittest.skip('can\'t pass because view is a simple Django view and response is an ImmediateResponse')
+ def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
+ """If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
+ resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
+ self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
+
+ def test_specified_renderer_serializes_content_on_format_query(self):
+ """If a 'format' query is specified, the renderer with the matching
+ format attribute should serialize the response."""
+ resp = self.client.get('/?format=%s' % RendererB.format)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_specified_renderer_serializes_content_on_format_kwargs(self):
+ """If a 'format' keyword arg is specified, the renderer with the matching
+ format attribute should serialize the response."""
+ resp = self.client.get('/something.formatb')
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+ def test_specified_renderer_is_used_on_format_query_with_matching_accept(self):
+ """If both a 'format' query and a matching Accept header specified,
+ the renderer with the matching format attribute should serialize the response."""
+ resp = self.client.get('/?format=%s' % RendererB.format,
+ HTTP_ACCEPT=RendererB.media_type)
+ self.assertEquals(resp['Content-Type'], RendererB.media_type)
+ self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+
+
+class Issue122Tests(TestCase):
+ """
+ Tests that covers #122.
+ """
+ urls = 'rest_framework.tests.response'
+
+ def test_only_html_renderer(self):
+ """
+ Test if no infinite recursion occurs.
+ """
+ self.client.get('/html')
+
+ def test_html_renderer_is_first(self):
+ """
+ Test if no infinite recursion occurs.
+ """
+ self.client.get('/html1')
diff --git a/rest_framework/tests/reverse.py b/rest_framework/tests/reverse.py
new file mode 100644
index 00000000..fd9a7d64
--- /dev/null
+++ b/rest_framework/tests/reverse.py
@@ -0,0 +1,26 @@
+from django.conf.urls.defaults import patterns, url
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework.reverse import reverse
+
+factory = RequestFactory()
+
+
+def null_view(request):
+ pass
+
+urlpatterns = patterns('',
+ url(r'^view$', null_view, name='view'),
+)
+
+
+class ReverseTests(TestCase):
+ """
+ Tests for fully qualifed URLs when using `reverse`.
+ """
+ urls = 'rest_framework.tests.reverse'
+
+ def test_reversed_urls_are_fully_qualified(self):
+ request = factory.get('/view')
+ url = reverse('view', request=request)
+ self.assertEqual(url, 'http://testserver/view')
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
new file mode 100644
index 00000000..d4b43862
--- /dev/null
+++ b/rest_framework/tests/serializer.py
@@ -0,0 +1,500 @@
+import datetime
+from django.test import TestCase
+from rest_framework import serializers
+from rest_framework.tests.models import *
+
+
+class SubComment(object):
+ def __init__(self, sub_comment):
+ self.sub_comment = sub_comment
+
+
+class Comment(object):
+ def __init__(self, email, content, created):
+ self.email = email
+ self.content = content
+ self.created = created or datetime.datetime.now()
+
+ def __eq__(self, other):
+ return all([getattr(self, attr) == getattr(other, attr)
+ for attr in ('email', 'content', 'created')])
+
+ def get_sub_comment(self):
+ sub_comment = SubComment('And Merry Christmas!')
+ return sub_comment
+
+
+class CommentSerializer(serializers.Serializer):
+ email = serializers.EmailField()
+ content = serializers.CharField(max_length=1000)
+ created = serializers.DateTimeField()
+ sub_comment = serializers.Field(source='get_sub_comment.sub_comment')
+
+ def restore_object(self, data, instance=None):
+ if instance is None:
+ return Comment(**data)
+ for key, val in data.items():
+ setattr(instance, key, val)
+ return instance
+
+
+class ActionItemSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ActionItem
+
+
+class PersonSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Person
+
+
+class BasicTests(TestCase):
+ def setUp(self):
+ self.comment = Comment(
+ 'tom@example.com',
+ 'Happy new year!',
+ datetime.datetime(2012, 1, 1)
+ )
+ self.data = {
+ 'email': 'tom@example.com',
+ 'content': 'Happy new year!',
+ 'created': datetime.datetime(2012, 1, 1),
+ 'sub_comment': 'This wont change'
+ }
+ self.expected = {
+ 'email': 'tom@example.com',
+ 'content': 'Happy new year!',
+ 'created': datetime.datetime(2012, 1, 1),
+ 'sub_comment': 'And Merry Christmas!'
+ }
+
+ def test_empty(self):
+ serializer = CommentSerializer()
+ expected = {
+ 'email': '',
+ 'content': '',
+ 'created': None,
+ 'sub_comment': ''
+ }
+ self.assertEquals(serializer.data, expected)
+
+ def test_retrieve(self):
+ serializer = CommentSerializer(instance=self.comment)
+ self.assertEquals(serializer.data, self.expected)
+
+ def test_create(self):
+ serializer = CommentSerializer(self.data)
+ expected = self.comment
+ self.assertEquals(serializer.is_valid(), True)
+ self.assertEquals(serializer.object, expected)
+ self.assertFalse(serializer.object is expected)
+ self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
+
+ def test_update(self):
+ serializer = CommentSerializer(self.data, instance=self.comment)
+ expected = self.comment
+ self.assertEquals(serializer.is_valid(), True)
+ self.assertEquals(serializer.object, expected)
+ self.assertTrue(serializer.object is expected)
+ self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
+
+
+class ValidationTests(TestCase):
+ def setUp(self):
+ self.comment = Comment(
+ 'tom@example.com',
+ 'Happy new year!',
+ datetime.datetime(2012, 1, 1)
+ )
+ self.data = {
+ 'email': 'tom@example.com',
+ 'content': 'x' * 1001,
+ 'created': datetime.datetime(2012, 1, 1)
+ }
+ self.actionitem = ActionItem('Some to do item',
+ )
+
+ def test_create(self):
+ serializer = CommentSerializer(self.data)
+ self.assertEquals(serializer.is_valid(), False)
+ self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
+
+ def test_update(self):
+ serializer = CommentSerializer(self.data, instance=self.comment)
+ self.assertEquals(serializer.is_valid(), False)
+ self.assertEquals(serializer.errors, {'content': [u'Ensure this value has at most 1000 characters (it has 1001).']})
+
+ def test_update_missing_field(self):
+ data = {
+ 'content': 'xxx',
+ 'created': datetime.datetime(2012, 1, 1)
+ }
+ serializer = CommentSerializer(data, instance=self.comment)
+ self.assertEquals(serializer.is_valid(), False)
+ self.assertEquals(serializer.errors, {'email': [u'This field is required.']})
+
+ def test_missing_bool_with_default(self):
+ """Make sure that a boolean value with a 'False' value is not
+ mistaken for not having a default."""
+ data = {
+ 'title': 'Some action item',
+ #No 'done' value.
+ }
+ serializer = ActionItemSerializer(data, instance=self.actionitem)
+ self.assertEquals(serializer.is_valid(), True)
+ self.assertEquals(serializer.errors, {})
+
+ def test_field_validation(self):
+
+ class CommentSerializerWithFieldValidator(CommentSerializer):
+
+ def validate_content(self, attrs, source):
+ value = attrs[source]
+ if "test" not in value:
+ raise serializers.ValidationError("Test not in value")
+ return attrs
+
+ data = {
+ 'email': 'tom@example.com',
+ 'content': 'A test comment',
+ 'created': datetime.datetime(2012, 1, 1)
+ }
+
+ serializer = CommentSerializerWithFieldValidator(data)
+ self.assertTrue(serializer.is_valid())
+
+ data['content'] = 'This should not validate'
+
+ serializer = CommentSerializerWithFieldValidator(data)
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'content': [u'Test not in value']})
+
+ def test_cross_field_validation(self):
+
+ class CommentSerializerWithCrossFieldValidator(CommentSerializer):
+
+ def validate(self, attrs):
+ if attrs["email"] not in attrs["content"]:
+ raise serializers.ValidationError("Email address not in content")
+ return attrs
+
+ data = {
+ 'email': 'tom@example.com',
+ 'content': 'A comment from tom@example.com',
+ 'created': datetime.datetime(2012, 1, 1)
+ }
+
+ serializer = CommentSerializerWithCrossFieldValidator(data)
+ self.assertTrue(serializer.is_valid())
+
+ data['content'] = 'A comment from foo@bar.com'
+
+ serializer = CommentSerializerWithCrossFieldValidator(data)
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'non_field_errors': [u'Email address not in content']})
+
+ def test_null_is_true_fields(self):
+ """
+ Omitting a value for null-field should validate.
+ """
+ serializer = PersonSerializer({'name': 'marko'})
+ self.assertEquals(serializer.is_valid(), True)
+ self.assertEquals(serializer.errors, {})
+
+
+class MetadataTests(TestCase):
+ def test_empty(self):
+ serializer = CommentSerializer()
+ expected = {
+ 'email': serializers.CharField,
+ 'content': serializers.CharField,
+ 'created': serializers.DateTimeField
+ }
+ for field_name, field in expected.items():
+ self.assertTrue(isinstance(serializer.data.fields[field_name], field))
+
+
+class ManyToManyTests(TestCase):
+ def setUp(self):
+ class ManyToManySerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ManyToManyModel
+
+ self.serializer_class = ManyToManySerializer
+
+ # An anchor instance to use for the relationship
+ self.anchor = Anchor()
+ self.anchor.save()
+
+ # A model instance with a many to many relationship to the anchor
+ self.instance = ManyToManyModel()
+ self.instance.save()
+ self.instance.rel.add(self.anchor)
+
+ # A serialized representation of the model instance
+ self.data = {'id': 1, 'rel': [self.anchor.id]}
+
+ def test_retrieve(self):
+ """
+ Serialize an instance of a model with a ManyToMany relationship.
+ """
+ serializer = self.serializer_class(instance=self.instance)
+ expected = self.data
+ self.assertEquals(serializer.data, expected)
+
+ def test_create(self):
+ """
+ Create an instance of a model with a ManyToMany relationship.
+ """
+ data = {'rel': [self.anchor.id]}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ManyToManyModel.objects.all()), 2)
+ self.assertEquals(instance.pk, 2)
+ self.assertEquals(list(instance.rel.all()), [self.anchor])
+
+ def test_update(self):
+ """
+ Update an instance of a model with a ManyToMany relationship.
+ """
+ new_anchor = Anchor()
+ new_anchor.save()
+ data = {'rel': [self.anchor.id, new_anchor.id]}
+ serializer = self.serializer_class(data, instance=self.instance)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ManyToManyModel.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ self.assertEquals(list(instance.rel.all()), [self.anchor, new_anchor])
+
+ def test_create_empty_relationship(self):
+ """
+ Create an instance of a model with a ManyToMany relationship,
+ containing no items.
+ """
+ data = {'rel': []}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ManyToManyModel.objects.all()), 2)
+ self.assertEquals(instance.pk, 2)
+ self.assertEquals(list(instance.rel.all()), [])
+
+ def test_update_empty_relationship(self):
+ """
+ Update an instance of a model with a ManyToMany relationship,
+ containing no items.
+ """
+ new_anchor = Anchor()
+ new_anchor.save()
+ data = {'rel': []}
+ serializer = self.serializer_class(data, instance=self.instance)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ManyToManyModel.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ self.assertEquals(list(instance.rel.all()), [])
+
+ def test_create_empty_relationship_flat_data(self):
+ """
+ Create an instance of a model with a ManyToMany relationship,
+ containing no items, using a representation that does not support
+ lists (eg form data).
+ """
+ data = {'rel': ''}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ManyToManyModel.objects.all()), 2)
+ self.assertEquals(instance.pk, 2)
+ self.assertEquals(list(instance.rel.all()), [])
+
+
+class ReadOnlyManyToManyTests(TestCase):
+ def setUp(self):
+ class ReadOnlyManyToManySerializer(serializers.ModelSerializer):
+ rel = serializers.ManyRelatedField(read_only=True)
+
+ class Meta:
+ model = ReadOnlyManyToManyModel
+
+ self.serializer_class = ReadOnlyManyToManySerializer
+
+ # An anchor instance to use for the relationship
+ self.anchor = Anchor()
+ self.anchor.save()
+
+ # A model instance with a many to many relationship to the anchor
+ self.instance = ReadOnlyManyToManyModel()
+ self.instance.save()
+ self.instance.rel.add(self.anchor)
+
+ # A serialized representation of the model instance
+ self.data = {'rel': [self.anchor.id], 'id': 1, 'text': 'anchor'}
+
+ def test_update(self):
+ """
+ Attempt to update an instance of a model with a ManyToMany
+ relationship. Not updated due to read_only=True
+ """
+ new_anchor = Anchor()
+ new_anchor.save()
+ data = {'rel': [self.anchor.id, new_anchor.id]}
+ serializer = self.serializer_class(data, instance=self.instance)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ReadOnlyManyToManyModel.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ # rel is still as original (1 entry)
+ self.assertEquals(list(instance.rel.all()), [self.anchor])
+
+ def test_update_without_relationship(self):
+ """
+ Attempt to update an instance of a model where many to ManyToMany
+ relationship is not supplied. Not updated due to read_only=True
+ """
+ new_anchor = Anchor()
+ new_anchor.save()
+ data = {}
+ serializer = self.serializer_class(data, instance=self.instance)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(ReadOnlyManyToManyModel.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ # rel is still as original (1 entry)
+ self.assertEquals(list(instance.rel.all()), [self.anchor])
+
+
+class DefaultValueTests(TestCase):
+ def setUp(self):
+ class DefaultValueSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = DefaultValueModel
+
+ self.serializer_class = DefaultValueSerializer
+ self.objects = DefaultValueModel.objects
+
+ def test_create_using_default(self):
+ data = {}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(self.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ self.assertEquals(instance.text, 'foobar')
+
+ def test_create_overriding_default(self):
+ data = {'text': 'overridden'}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(self.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ self.assertEquals(instance.text, 'overridden')
+
+
+class CallableDefaultValueTests(TestCase):
+ def setUp(self):
+ class CallableDefaultValueSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = CallableDefaultValueModel
+
+ self.serializer_class = CallableDefaultValueSerializer
+ self.objects = CallableDefaultValueModel.objects
+
+ def test_create_using_default(self):
+ data = {}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(self.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ self.assertEquals(instance.text, 'foobar')
+
+ def test_create_overriding_default(self):
+ data = {'text': 'overridden'}
+ serializer = self.serializer_class(data)
+ self.assertEquals(serializer.is_valid(), True)
+ instance = serializer.save()
+ self.assertEquals(len(self.objects.all()), 1)
+ self.assertEquals(instance.pk, 1)
+ self.assertEquals(instance.text, 'overridden')
+
+
+class ManyRelatedTests(TestCase):
+ def setUp(self):
+
+ class BlogPostCommentSerializer(serializers.Serializer):
+ text = serializers.CharField()
+
+ class BlogPostSerializer(serializers.Serializer):
+ title = serializers.CharField()
+ comments = BlogPostCommentSerializer(source='blogpostcomment_set')
+
+ self.serializer_class = BlogPostSerializer
+
+ def test_reverse_relations(self):
+ post = BlogPost.objects.create(title="Test blog post")
+ post.blogpostcomment_set.create(text="I hate this blog post")
+ post.blogpostcomment_set.create(text="I love this blog post")
+
+ serializer = self.serializer_class(instance=post)
+ expected = {
+ 'title': 'Test blog post',
+ 'comments': [
+ {'text': 'I hate this blog post'},
+ {'text': 'I love this blog post'}
+ ]
+ }
+
+ self.assertEqual(serializer.data, expected)
+
+
+# Test for issue #324
+class BlankFieldTests(TestCase):
+ def setUp(self):
+
+ class BlankFieldModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = BlankFieldModel
+
+ class BlankFieldSerializer(serializers.Serializer):
+ title = serializers.CharField(blank=True)
+
+ class NotBlankFieldModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = BasicModel
+
+ class NotBlankFieldSerializer(serializers.Serializer):
+ title = serializers.CharField()
+
+ self.model_serializer_class = BlankFieldModelSerializer
+ self.serializer_class = BlankFieldSerializer
+ self.not_blank_model_serializer_class = NotBlankFieldModelSerializer
+ self.not_blank_serializer_class = NotBlankFieldSerializer
+ self.data = {'title': ''}
+
+ def test_create_blank_field(self):
+ serializer = self.serializer_class(self.data)
+ self.assertEquals(serializer.is_valid(), True)
+
+ def test_create_model_blank_field(self):
+ serializer = self.model_serializer_class(self.data)
+ self.assertEquals(serializer.is_valid(), True)
+
+ def test_create_not_blank_field(self):
+ """
+ Test to ensure blank data in a field not marked as blank=True
+ is considered invalid in a non-model serializer
+ """
+ serializer = self.not_blank_serializer_class(self.data)
+ self.assertEquals(serializer.is_valid(), False)
+
+ def test_create_model_not_blank_field(self):
+ """
+ Test to ensure blank data in a field not marked as blank=True
+ is considered invalid in a model serializer
+ """
+ serializer = self.not_blank_model_serializer_class(self.data)
+ self.assertEquals(serializer.is_valid(), False)
diff --git a/djangorestframework/tests/status.py b/rest_framework/tests/status.py
index 53ecae11..30df5cef 100644
--- a/djangorestframework/tests/status.py
+++ b/rest_framework/tests/status.py
@@ -1,6 +1,6 @@
"""Tests for the status module"""
from django.test import TestCase
-from djangorestframework import status
+from rest_framework import status
class TestStatus(TestCase):
diff --git a/djangorestframework/tests/testcases.py b/rest_framework/tests/testcases.py
index 51b4afbc..c90224aa 100644
--- a/djangorestframework/tests/testcases.py
+++ b/rest_framework/tests/testcases.py
@@ -59,5 +59,5 @@ class SettingsTestCase(TestCase):
class TestModelsTestCase(SettingsTestCase):
def setUp(self, *args, **kwargs):
- installed_apps = tuple(settings.INSTALLED_APPS) + ('djangorestframework.tests',)
+ installed_apps = tuple(settings.INSTALLED_APPS) + ('rest_framework.tests',)
self.settings_manager.set(INSTALLED_APPS=installed_apps)
diff --git a/rest_framework/tests/tests.py b/rest_framework/tests/tests.py
new file mode 100644
index 00000000..adeaf6da
--- /dev/null
+++ b/rest_framework/tests/tests.py
@@ -0,0 +1,13 @@
+"""
+Force import of all modules in this package in order to get the standard test
+runner to pick up the tests. Yowzers.
+"""
+import os
+
+modules = [filename.rsplit('.', 1)[0]
+ for filename in os.listdir(os.path.dirname(__file__))
+ if filename.endswith('.py') and not filename.startswith('_')]
+__test__ = dict()
+
+for module in modules:
+ exec("from rest_framework.tests.%s import *" % module)
diff --git a/djangorestframework/tests/throttling.py b/rest_framework/tests/throttling.py
index 7fdc6491..0b94c25b 100644
--- a/djangorestframework/tests/throttling.py
+++ b/rest_framework/tests/throttling.py
@@ -6,32 +6,38 @@ from django.test import TestCase
from django.contrib.auth.models import User
from django.core.cache import cache
-from djangorestframework.compat import RequestFactory
-from djangorestframework.views import View
-from djangorestframework.permissions import PerUserThrottling, PerViewThrottling, PerResourceThrottling
-from djangorestframework.resources import FormResource
+from django.test.client import RequestFactory
+from rest_framework.views import APIView
+from rest_framework.throttling import UserRateThrottle
+from rest_framework.response import Response
-class MockView(View):
- permissions = ( PerUserThrottling, )
- throttle = '3/sec'
- def get(self, request):
- return 'foo'
+class User3SecRateThrottle(UserRateThrottle):
+ rate = '3/sec'
+ scope = 'seconds'
+
-class MockView_PerViewThrottling(MockView):
- permissions = ( PerViewThrottling, )
+class User3MinRateThrottle(UserRateThrottle):
+ rate = '3/min'
+ scope = 'minutes'
-class MockView_PerResourceThrottling(MockView):
- permissions = ( PerResourceThrottling, )
- resource = FormResource
-class MockView_MinuteThrottling(MockView):
- throttle = '3/min'
+class MockView(APIView):
+ throttle_classes = (User3SecRateThrottle,)
+
+ def get(self, request):
+ return Response('foo')
+class MockView_MinuteThrottling(APIView):
+ throttle_classes = (User3MinRateThrottle,)
+
+ def get(self, request):
+ return Response('foo')
+
class ThrottlingTests(TestCase):
- urls = 'djangorestframework.tests.throttling'
+ urls = 'rest_framework.tests.throttling'
def setUp(self):
"""
@@ -47,13 +53,13 @@ class ThrottlingTests(TestCase):
request = self.factory.get('/')
for dummy in range(4):
response = MockView.as_view()(request)
- self.assertEqual(503, response.status_code)
+ self.assertEqual(429, response.status_code)
def set_throttle_timer(self, view, value):
"""
Explicitly set the timer, overriding time.time()
"""
- view.permissions[0].timer = lambda self: value
+ view.throttle_classes[0].timer = lambda self: value
def test_request_throttling_expires(self):
"""
@@ -64,7 +70,7 @@ class ThrottlingTests(TestCase):
request = self.factory.get('/')
for dummy in range(4):
response = MockView.as_view()(request)
- self.assertEqual(503, response.status_code)
+ self.assertEqual(429, response.status_code)
# Advance the timer by one second
self.set_throttle_timer(MockView, 1)
@@ -88,19 +94,6 @@ class ThrottlingTests(TestCase):
"""
self.ensure_is_throttled(MockView, 200)
- def test_request_throttling_is_per_view(self):
- """
- Ensure request rate is limited globally per View for PerViewThrottles
- """
- self.ensure_is_throttled(MockView_PerViewThrottling, 503)
-
- def test_request_throttling_is_per_resource(self):
- """
- Ensure request rate is limited globally per Resource for PerResourceThrottles
- """
- self.ensure_is_throttled(MockView_PerResourceThrottling, 503)
-
-
def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers):
"""
Ensure the response returns an X-Throttle field with status and next attributes
@@ -110,17 +103,20 @@ class ThrottlingTests(TestCase):
for timer, expect in expected_headers:
self.set_throttle_timer(view, timer)
response = view.as_view()(request)
- self.assertEquals(response['X-Throttle'], expect)
+ if expect is not None:
+ self.assertEquals(response['X-Throttle-Wait-Seconds'], expect)
+ else:
+ self.assertFalse('X-Throttle-Wait-Seconds' in response.headers)
def test_seconds_fields(self):
"""
Ensure for second based throttles.
"""
self.ensure_response_header_contains_proper_throttle_field(MockView,
- ((0, 'status=SUCCESS; next=0.33 sec'),
- (0, 'status=SUCCESS; next=0.50 sec'),
- (0, 'status=SUCCESS; next=1.00 sec'),
- (0, 'status=FAILURE; next=1.00 sec')
+ ((0, None),
+ (0, None),
+ (0, None),
+ (0, '1')
))
def test_minutes_fields(self):
@@ -128,10 +124,10 @@ class ThrottlingTests(TestCase):
Ensure for minute based throttles.
"""
self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling,
- ((0, 'status=SUCCESS; next=20.00 sec'),
- (0, 'status=SUCCESS; next=30.00 sec'),
- (0, 'status=SUCCESS; next=60.00 sec'),
- (0, 'status=FAILURE; next=60.00 sec')
+ ((0, None),
+ (0, None),
+ (0, None),
+ (0, '60')
))
def test_next_rate_remains_constant_if_followed(self):
@@ -140,9 +136,9 @@ class ThrottlingTests(TestCase):
the throttling rate should stay constant.
"""
self.ensure_response_header_contains_proper_throttle_field(MockView_MinuteThrottling,
- ((0, 'status=SUCCESS; next=20.00 sec'),
- (20, 'status=SUCCESS; next=20.00 sec'),
- (40, 'status=SUCCESS; next=20.00 sec'),
- (60, 'status=SUCCESS; next=20.00 sec'),
- (80, 'status=SUCCESS; next=20.00 sec')
+ ((0, None),
+ (20, None),
+ (40, None),
+ (60, None),
+ (80, None)
))
diff --git a/rest_framework/tests/validators.py b/rest_framework/tests/validators.py
new file mode 100644
index 00000000..c032985e
--- /dev/null
+++ b/rest_framework/tests/validators.py
@@ -0,0 +1,329 @@
+# from django import forms
+# from django.db import models
+# from django.test import TestCase
+# from rest_framework.response import ImmediateResponse
+# from rest_framework.views import View
+
+
+# class TestDisabledValidations(TestCase):
+# """Tests on FormValidator with validation disabled by setting form to None"""
+
+# def test_disabled_form_validator_returns_content_unchanged(self):
+# """If the view's form attribute is None then FormValidator(view).validate_request(content, None)
+# should just return the content unmodified."""
+# class DisabledFormResource(FormResource):
+# form = None
+
+# class MockView(View):
+# resource = DisabledFormResource
+
+# view = MockView()
+# content = {'qwerty': 'uiop'}
+# self.assertEqual(FormResource(view).validate_request(content, None), content)
+
+# def test_disabled_form_validator_get_bound_form_returns_none(self):
+# """If the view's form attribute is None on then
+# FormValidator(view).get_bound_form(content) should just return None."""
+# class DisabledFormResource(FormResource):
+# form = None
+
+# class MockView(View):
+# resource = DisabledFormResource
+
+# view = MockView()
+# content = {'qwerty': 'uiop'}
+# self.assertEqual(FormResource(view).get_bound_form(content), None)
+
+# def test_disabled_model_form_validator_returns_content_unchanged(self):
+# """If the view's form is None and does not have a Resource with a model set then
+# ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
+
+# class DisabledModelFormView(View):
+# resource = ModelResource
+
+# view = DisabledModelFormView()
+# content = {'qwerty': 'uiop'}
+# self.assertEqual(ModelResource(view).get_bound_form(content), None)
+
+# def test_disabled_model_form_validator_get_bound_form_returns_none(self):
+# """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
+# class DisabledModelFormView(View):
+# resource = ModelResource
+
+# view = DisabledModelFormView()
+# content = {'qwerty': 'uiop'}
+# self.assertEqual(ModelResource(view).get_bound_form(content), None)
+
+
+# class TestNonFieldErrors(TestCase):
+# """Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
+
+# def test_validate_failed_due_to_non_field_error_returns_appropriate_message(self):
+# """If validation fails with a non-field error, ensure the response a non-field error"""
+# class MockForm(forms.Form):
+# field1 = forms.CharField(required=False)
+# field2 = forms.CharField(required=False)
+# ERROR_TEXT = 'You may not supply both field1 and field2'
+
+# def clean(self):
+# if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data:
+# raise forms.ValidationError(self.ERROR_TEXT)
+# return self.cleaned_data
+
+# class MockResource(FormResource):
+# form = MockForm
+
+# class MockView(View):
+# pass
+
+# view = MockView()
+# content = {'field1': 'example1', 'field2': 'example2'}
+# try:
+# MockResource(view).validate_request(content, None)
+# except ImmediateResponse, exc:
+# response = exc.response
+# self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
+# else:
+# self.fail('ImmediateResponse was not raised')
+
+
+# class TestFormValidation(TestCase):
+# """Tests which check basic form validation.
+# Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
+# (ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)"""
+# def setUp(self):
+# class MockForm(forms.Form):
+# qwerty = forms.CharField(required=True)
+
+# class MockFormResource(FormResource):
+# form = MockForm
+
+# class MockModelResource(ModelResource):
+# form = MockForm
+
+# class MockFormView(View):
+# resource = MockFormResource
+
+# class MockModelFormView(View):
+# resource = MockModelResource
+
+# self.MockFormResource = MockFormResource
+# self.MockModelResource = MockModelResource
+# self.MockFormView = MockFormView
+# self.MockModelFormView = MockModelFormView
+
+# def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
+# """If the content is already valid and clean then validate(content) should just return the content unmodified."""
+# content = {'qwerty': 'uiop'}
+# self.assertEqual(validator.validate_request(content, None), content)
+
+# def validation_failure_raises_response_exception(self, validator):
+# """If form validation fails a ResourceException 400 (Bad Request) should be raised."""
+# content = {}
+# self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
+
+# def validation_does_not_allow_extra_fields_by_default(self, validator):
+# """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
+# It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
+# broken clients more easily (eg submitting content with a misnamed field)"""
+# content = {'qwerty': 'uiop', 'extra': 'extra'}
+# self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
+
+# def validation_allows_extra_fields_if_explicitly_set(self, validator):
+# """If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
+# content = {'qwerty': 'uiop', 'extra': 'extra'}
+# validator._validate(content, None, allowed_extra_fields=('extra',))
+
+# def validation_allows_unknown_fields_if_explicitly_allowed(self, validator):
+# """If we set ``unknown_form_fields`` on the form resource, then don't
+# raise errors on unexpected request data"""
+# content = {'qwerty': 'uiop', 'extra': 'extra'}
+# validator.allow_unknown_form_fields = True
+# self.assertEqual({'qwerty': u'uiop'},
+# validator.validate_request(content, None),
+# "Resource didn't accept unknown fields.")
+# validator.allow_unknown_form_fields = False
+
+# def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
+# """If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
+# content = {'qwerty': 'uiop'}
+# self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
+
+# def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
+# """If validation fails due to no content, ensure the response contains a single non-field error"""
+# content = {}
+# try:
+# validator.validate_request(content, None)
+# except ImmediateResponse, exc:
+# response = exc.response
+# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
+# else:
+# self.fail('ResourceException was not raised')
+
+# def validation_failed_due_to_field_error_returns_appropriate_message(self, validator):
+# """If validation fails due to a field error, ensure the response contains a single field error"""
+# content = {'qwerty': ''}
+# try:
+# validator.validate_request(content, None)
+# except ImmediateResponse, exc:
+# response = exc.response
+# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
+# else:
+# self.fail('ResourceException was not raised')
+
+# def validation_failed_due_to_invalid_field_returns_appropriate_message(self, validator):
+# """If validation fails due to an invalid field, ensure the response contains a single field error"""
+# content = {'qwerty': 'uiop', 'extra': 'extra'}
+# try:
+# validator.validate_request(content, None)
+# except ImmediateResponse, exc:
+# response = exc.response
+# self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
+# else:
+# self.fail('ResourceException was not raised')
+
+# def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator):
+# """If validation for multiple reasons, ensure the response contains each error"""
+# content = {'qwerty': '', 'extra': 'extra'}
+# try:
+# validator.validate_request(content, None)
+# except ImmediateResponse, exc:
+# response = exc.response
+# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
+# 'extra': ['This field does not exist.']}})
+# else:
+# self.fail('ResourceException was not raised')
+
+# # Tests on FormResource
+
+# def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
+
+# def test_form_validation_failure_raises_response_exception(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_failure_raises_response_exception(validator)
+
+# def test_validation_does_not_allow_extra_fields_by_default(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_does_not_allow_extra_fields_by_default(validator)
+
+# def test_validation_allows_extra_fields_if_explicitly_set(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_allows_extra_fields_if_explicitly_set(validator)
+
+# def test_validation_allows_unknown_fields_if_explicitly_allowed(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_allows_unknown_fields_if_explicitly_allowed(validator)
+
+# def test_validation_does_not_require_extra_fields_if_explicitly_set(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
+
+# def test_validation_failed_due_to_no_content_returns_appropriate_message(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
+
+# def test_validation_failed_due_to_field_error_returns_appropriate_message(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
+
+# def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
+
+# def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
+# validator = self.MockFormResource(self.MockFormView())
+# self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
+
+# # Same tests on ModelResource
+
+# def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
+
+# def test_modelform_validation_failure_raises_response_exception(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_failure_raises_response_exception(validator)
+
+# def test_modelform_validation_does_not_allow_extra_fields_by_default(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_does_not_allow_extra_fields_by_default(validator)
+
+# def test_modelform_validation_allows_extra_fields_if_explicitly_set(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_allows_extra_fields_if_explicitly_set(validator)
+
+# def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
+
+# def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
+
+# def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
+
+# def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
+
+# def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
+# validator = self.MockModelResource(self.MockModelFormView())
+# self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
+
+
+# class TestModelFormValidator(TestCase):
+# """Tests specific to ModelFormValidatorMixin"""
+
+# def setUp(self):
+# """Create a validator for a model with two fields and a property."""
+# class MockModel(models.Model):
+# qwerty = models.CharField(max_length=256)
+# uiop = models.CharField(max_length=256, blank=True)
+
+# @property
+# def read_only(self):
+# return 'read only'
+
+# class MockResource(ModelResource):
+# model = MockModel
+
+# class MockView(View):
+# resource = MockResource
+
+# self.validator = MockResource(MockView)
+
+# def test_property_fields_are_allowed_on_model_forms(self):
+# """Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
+# content = {'qwerty': 'example', 'uiop': 'example', 'read_only': 'read only'}
+# self.assertEqual(self.validator.validate_request(content, None), content)
+
+# def test_property_fields_are_not_required_on_model_forms(self):
+# """Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
+# content = {'qwerty': 'example', 'uiop': 'example'}
+# self.assertEqual(self.validator.validate_request(content, None), content)
+
+# def test_extra_fields_not_allowed_on_model_forms(self):
+# """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
+# It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
+# broken clients more easily (eg submitting content with a misnamed field)"""
+# content = {'qwerty': 'example', 'uiop': 'example', 'read_only': 'read only', 'extra': 'extra'}
+# self.assertRaises(ImmediateResponse, self.validator.validate_request, content, None)
+
+# def test_validate_requires_fields_on_model_forms(self):
+# """If some (otherwise valid) content includes fields that are not in the form then validation should fail.
+# It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
+# broken clients more easily (eg submitting content with a misnamed field)"""
+# content = {'read_only': 'read only'}
+# self.assertRaises(ImmediateResponse, self.validator.validate_request, content, None)
+
+# def test_validate_does_not_require_blankable_fields_on_model_forms(self):
+# """Test standard ModelForm validation behaviour - fields with blank=True are not required."""
+# content = {'qwerty': 'example', 'read_only': 'read only'}
+# self.validator.validate_request(content, None)
+
+# def test_model_form_validator_uses_model_forms(self):
+# self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))
diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py
new file mode 100644
index 00000000..43365e07
--- /dev/null
+++ b/rest_framework/tests/views.py
@@ -0,0 +1,97 @@
+import copy
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework import status
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.settings import api_settings
+from rest_framework.views import APIView
+
+factory = RequestFactory()
+
+
+class BasicView(APIView):
+ def get(self, request, *args, **kwargs):
+ return Response({'method': 'GET'})
+
+ def post(self, request, *args, **kwargs):
+ return Response({'method': 'POST', 'data': request.DATA})
+
+
+@api_view(['GET', 'POST', 'PUT'])
+def basic_view(request):
+ if request.method == 'GET':
+ return {'method': 'GET'}
+ elif request.method == 'POST':
+ return {'method': 'POST', 'data': request.DATA}
+ elif request.method == 'PUT':
+ return {'method': 'PUT', 'data': request.DATA}
+
+
+def sanitise_json_error(error_dict):
+ """
+ Exact contents of JSON error messages depend on the installed version
+ of json.
+ """
+ ret = copy.copy(error_dict)
+ chop = len('JSON parse error - No JSON object could be decoded')
+ ret['detail'] = ret['detail'][:chop]
+ return ret
+
+
+class ClassBasedViewIntegrationTests(TestCase):
+ def setUp(self):
+ self.view = BasicView.as_view()
+
+ def test_400_parse_error(self):
+ request = factory.post('/', 'f00bar', content_type='application/json')
+ response = self.view(request)
+ expected = {
+ 'detail': u'JSON parse error - No JSON object could be decoded'
+ }
+ self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertEquals(sanitise_json_error(response.data), expected)
+
+ def test_400_parse_error_tunneled_content(self):
+ content = 'f00bar'
+ content_type = 'application/json'
+ form_data = {
+ api_settings.FORM_CONTENT_OVERRIDE: content,
+ api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
+ }
+ request = factory.post('/', form_data)
+ response = self.view(request)
+ expected = {
+ 'detail': u'JSON parse error - No JSON object could be decoded'
+ }
+ self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertEquals(sanitise_json_error(response.data), expected)
+
+
+class FunctionBasedViewIntegrationTests(TestCase):
+ def setUp(self):
+ self.view = basic_view
+
+ def test_400_parse_error(self):
+ request = factory.post('/', 'f00bar', content_type='application/json')
+ response = self.view(request)
+ expected = {
+ 'detail': u'JSON parse error - No JSON object could be decoded'
+ }
+ self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertEquals(sanitise_json_error(response.data), expected)
+
+ def test_400_parse_error_tunneled_content(self):
+ content = 'f00bar'
+ content_type = 'application/json'
+ form_data = {
+ api_settings.FORM_CONTENT_OVERRIDE: content,
+ api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
+ }
+ request = factory.post('/', form_data)
+ response = self.view(request)
+ expected = {
+ 'detail': u'JSON parse error - No JSON object could be decoded'
+ }
+ self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertEquals(sanitise_json_error(response.data), expected)
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
new file mode 100644
index 00000000..8fe64248
--- /dev/null
+++ b/rest_framework/throttling.py
@@ -0,0 +1,208 @@
+import time
+from django.core.cache import cache
+from rest_framework import exceptions
+from rest_framework.settings import api_settings
+
+
+class BaseThrottle(object):
+ """
+ Rate throttling of requests.
+ """
+ def allow_request(self, request, view):
+ """
+ Return `True` if the request should be allowed, `False` otherwise.
+ """
+ raise NotImplementedError('.allow_request() must be overridden')
+
+ def wait(self):
+ """
+ Optionally, return a recommended number of seconds to wait before
+ the next request.
+ """
+ return None
+
+
+class SimpleRateThrottle(BaseThrottle):
+ """
+ A simple cache implementation, that only requires `.get_cache_key()`
+ to be overridden.
+
+ The rate (requests / seconds) is set by a :attr:`throttle` attribute
+ on the :class:`.View` class. The attribute is a string of the form 'number of
+ requests/period'.
+
+ Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
+
+ Previous request information used for throttling is stored in the cache.
+ """
+
+ timer = time.time
+ settings = api_settings
+ cache_format = 'throtte_%(scope)s_%(ident)s'
+ scope = None
+
+ def __init__(self):
+ if not getattr(self, 'rate', None):
+ self.rate = self.get_rate()
+ self.num_requests, self.duration = self.parse_rate(self.rate)
+
+ def get_cache_key(self, request, view):
+ """
+ Should return a unique cache-key which can be used for throttling.
+ Must be overridden.
+
+ May return `None` if the request should not be throttled.
+ """
+ raise NotImplementedError('.get_cache_key() must be overridden')
+
+ def get_rate(self):
+ """
+ Determine the string representation of the allowed request rate.
+ """
+ if not getattr(self, 'scope', None):
+ msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
+ self.__class__.__name__)
+ raise exceptions.ConfigurationError(msg)
+
+ try:
+ return self.settings.DEFAULT_THROTTLE_RATES[self.scope]
+ except KeyError:
+ msg = "No default throttle rate set for '%s' scope" % self.scope
+ raise exceptions.ConfigurationError(msg)
+
+ def parse_rate(self, rate):
+ """
+ Given the request rate string, return a two tuple of:
+ <allowed number of requests>, <period of time in seconds>
+ """
+ if rate is None:
+ return (None, None)
+ num, period = rate.split('/')
+ num_requests = int(num)
+ duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
+ return (num_requests, duration)
+
+ def allow_request(self, request, view):
+ """
+ Implement the check to see if the request should be throttled.
+
+ On success calls `throttle_success`.
+ On failure calls `throttle_failure`.
+ """
+ if self.rate is None:
+ return True
+
+ self.key = self.get_cache_key(request, view)
+ self.history = cache.get(self.key, [])
+ self.now = self.timer()
+
+ # Drop any requests from the history which have now passed the
+ # throttle duration
+ while self.history and self.history[-1] <= self.now - self.duration:
+ self.history.pop()
+ if len(self.history) >= self.num_requests:
+ return self.throttle_failure()
+ return self.throttle_success()
+
+ def throttle_success(self):
+ """
+ Inserts the current request's timestamp along with the key
+ into the cache.
+ """
+ self.history.insert(0, self.now)
+ cache.set(self.key, self.history, self.duration)
+ return True
+
+ def throttle_failure(self):
+ """
+ Called when a request to the API has failed due to throttling.
+ """
+ return False
+
+ def wait(self):
+ """
+ Returns the recommended next request time in seconds.
+ """
+ if self.history:
+ remaining_duration = self.duration - (self.now - self.history[-1])
+ else:
+ remaining_duration = self.duration
+
+ available_requests = self.num_requests - len(self.history) + 1
+
+ return remaining_duration / float(available_requests)
+
+
+class AnonRateThrottle(SimpleRateThrottle):
+ """
+ Limits the rate of API calls that may be made by a anonymous users.
+
+ The IP address of the request will be used as the unique cache key.
+ """
+ scope = 'anon'
+
+ def get_cache_key(self, request, view):
+ if request.user.is_authenticated():
+ return None # Only throttle unauthenticated requests.
+
+ ident = request.META.get('REMOTE_ADDR', None)
+
+ return self.cache_format % {
+ 'scope': self.scope,
+ 'ident': ident
+ }
+
+
+class UserRateThrottle(SimpleRateThrottle):
+ """
+ Limits the rate of API calls that may be made by a given user.
+
+ The user id will be used as a unique cache key if the user is
+ authenticated. For anonymous requests, the IP address of the request will
+ be used.
+ """
+ scope = 'user'
+
+ def get_cache_key(self, request, view):
+ if request.user.is_authenticated():
+ ident = request.user.id
+ else:
+ ident = request.META.get('REMOTE_ADDR', None)
+
+ return self.cache_format % {
+ 'scope': self.scope,
+ 'ident': ident
+ }
+
+
+class ScopedRateThrottle(SimpleRateThrottle):
+ """
+ Limits the rate of API calls by different amounts for various parts of
+ the API. Any view that has the `throttle_scope` property set will be
+ throttled. The unique cache key will be generated by concatenating the
+ user id of the request, and the scope of the view being accessed.
+ """
+ scope_attr = 'throttle_scope'
+
+ def get_cache_key(self, request, view):
+ """
+ If `view.throttle_scope` is not set, don't apply this throttle.
+
+ Otherwise generate the unique cache key by concatenating the user id
+ with the '.throttle_scope` property of the view.
+ """
+ scope = getattr(view, self.scope_attr, None)
+
+ if not scope:
+ # Only throttle views if `.throttle_scope` is set on the view.
+ return None
+
+ if request.user.is_authenticated():
+ ident = request.user.id
+ else:
+ ident = request.META.get('REMOTE_ADDR', None)
+
+ return self.cache_format % {
+ 'scope': scope,
+ 'ident': ident
+ }
diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py
new file mode 100644
index 00000000..316ccd19
--- /dev/null
+++ b/rest_framework/urlpatterns.py
@@ -0,0 +1,42 @@
+from django.conf.urls.defaults import url
+from rest_framework.settings import api_settings
+
+
+def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
+ """
+ Supplement existing urlpatterns with corrosponding patterns that also
+ include a '.format' suffix. Retains urlpattern ordering.
+
+ urlpatterns:
+ A list of URL patterns.
+
+ suffix_required:
+ If `True`, only suffixed URLs will be generated, and non-suffixed
+ URLs will not be used. Defaults to `False`.
+
+ allowed:
+ An optional tuple/list of allowed suffixes. eg ['json', 'api']
+ Defaults to `None`, which allows any suffix.
+ """
+ suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG
+ if allowed:
+ if len(allowed) == 1:
+ allowed_pattern = allowed[0]
+ else:
+ allowed_pattern = '(%s)' % '|'.join(allowed)
+ suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern)
+ else:
+ suffix_pattern = r'\.(?P<%s>[a-z]+)$' % suffix_kwarg
+
+ ret = []
+ for urlpattern in urlpatterns:
+ # Form our complementing '.format' urlpattern
+ regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern
+ view = urlpattern._callback or urlpattern._callback_str
+ kwargs = urlpattern.default_args
+ name = urlpattern.name
+ # Add in both the existing and the new urlpattern
+ if not suffix_required:
+ ret.append(urlpattern)
+ ret.append(url(regex, view, kwargs, name))
+ return ret
diff --git a/rest_framework/urls.py b/rest_framework/urls.py
new file mode 100644
index 00000000..1a81101f
--- /dev/null
+++ b/rest_framework/urls.py
@@ -0,0 +1,23 @@
+"""
+Login and logout views for the browseable API.
+
+Add these to your root URLconf if you're using the browseable API and
+your API requires authentication.
+
+The urls must be namespaced as 'rest_framework', and you should make sure
+your authentication settings include `SessionAuthentication`.
+
+ urlpatterns = patterns('',
+ ...
+ url(r'^auth', include('rest_framework.urls', namespace='rest_framework'))
+ )
+"""
+from django.conf.urls.defaults import patterns, url
+
+
+template_name = {'template_name': 'rest_framework/login.html'}
+
+urlpatterns = patterns('django.contrib.auth.views',
+ url(r'^login/$', 'login', template_name, name='login'),
+ url(r'^logout/$', 'logout', template_name, name='logout'),
+)
diff --git a/djangorestframework/utils/__init__.py b/rest_framework/utils/__init__.py
index ca16b261..a59fff45 100644
--- a/djangorestframework/utils/__init__.py
+++ b/rest_framework/utils/__init__.py
@@ -1,48 +1,10 @@
-import django
from django.utils.encoding import smart_unicode
from django.utils.xmlutils import SimplerXMLGenerator
-from django.core.urlresolvers import resolve
-from django.conf import settings
-
-from djangorestframework.compat import StringIO
+from rest_framework.compat import StringIO
import re
import xml.etree.ElementTree as ET
-from mediatypes import media_type_matches, is_form_media_type
-from mediatypes import add_media_type_param, get_media_type_params, order_by_precedence
-
-MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
-
-def as_tuple(obj):
- """
- Given an object which may be a list/tuple, another object, or None,
- return that object in list form.
-
- IE:
- If the object is already a list/tuple just return it.
- If the object is not None, return it in a list with a single element.
- If the object is None return an empty list.
- """
- if obj is None:
- return ()
- elif isinstance(obj, list):
- return tuple(obj)
- elif isinstance(obj, tuple):
- return obj
- return (obj,)
-
-
-def url_resolves(url):
- """
- Return True if the given URL is mapped to a view in the urlconf, False otherwise.
- """
- try:
- resolve(url)
- except Exception:
- return False
- return True
-
# From xml2dict
class XML2Dict(object):
@@ -55,24 +17,23 @@ class XML2Dict(object):
# Save attrs and text, hope there will not be a child with same name
if node.text:
node_tree = node.text
- for (k,v) in node.attrib.items():
- k,v = self._namespace_split(k, v)
+ for (k, v) in node.attrib.items():
+ k, v = self._namespace_split(k, v)
node_tree[k] = v
#Save childrens
for child in node.getchildren():
tag, tree = self._namespace_split(child.tag, self._parse_node(child))
- if tag not in node_tree: # the first time, so store it in dict
+ if tag not in node_tree: # the first time, so store it in dict
node_tree[tag] = tree
continue
old = node_tree[tag]
if not isinstance(old, list):
node_tree.pop(tag)
- node_tree[tag] = [old] # multi times, so change old dict to a list
- node_tree[tag].append(tree) # add the new one
+ node_tree[tag] = [old] # multi times, so change old dict to a list
+ node_tree[tag].append(tree) # add the new one
return node_tree
-
def _namespace_split(self, tag, value):
"""
Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
@@ -135,5 +96,6 @@ class XMLRenderer():
xml.endDocument()
return stream.getvalue()
+
def dict2xml(input):
return XMLRenderer().dict2xml(input)
diff --git a/djangorestframework/utils/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py
index becdcad0..672d32a3 100644
--- a/djangorestframework/utils/breadcrumbs.py
+++ b/rest_framework/utils/breadcrumbs.py
@@ -4,7 +4,7 @@ from django.core.urlresolvers import resolve, get_script_prefix
def get_breadcrumbs(url):
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
- from djangorestframework.views import View
+ from rest_framework.views import APIView
def breadcrumbs_recursive(url, breadcrumbs_list, prefix):
"""Add tuples of (name, url) to the breadcrumbs list, progressively chomping off parts of the url."""
@@ -15,7 +15,7 @@ def get_breadcrumbs(url):
pass
else:
# Check if this is a REST framework view, and if so add it to the breadcrumbs
- if isinstance(getattr(view, 'cls_instance', None), View):
+ if isinstance(getattr(view, 'cls_instance', None), APIView):
breadcrumbs_list.insert(0, (view.cls_instance.get_name(), prefix + url))
if url == '':
diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py
new file mode 100644
index 00000000..2d1fb353
--- /dev/null
+++ b/rest_framework/utils/encoders.py
@@ -0,0 +1,91 @@
+"""
+Helper classes for parsers.
+"""
+import datetime
+import decimal
+import types
+from django.utils import simplejson as json
+from django.utils.datastructures import SortedDict
+from rest_framework.compat import timezone
+from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
+
+
+class JSONEncoder(json.JSONEncoder):
+ """
+ JSONEncoder subclass that knows how to encode date/time,
+ decimal types, and generators.
+ """
+ def default(self, o):
+ # For Date Time string spec, see ECMA 262
+ # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
+ if isinstance(o, datetime.datetime):
+ r = o.isoformat()
+ if o.microsecond:
+ r = r[:23] + r[26:]
+ if r.endswith('+00:00'):
+ r = r[:-6] + 'Z'
+ return r
+ elif isinstance(o, datetime.date):
+ return o.isoformat()
+ elif isinstance(o, datetime.time):
+ if timezone and timezone.is_aware(o):
+ raise ValueError("JSON can't represent timezone-aware times.")
+ r = o.isoformat()
+ if o.microsecond:
+ r = r[:12]
+ return r
+ elif isinstance(o, decimal.Decimal):
+ return str(o)
+ elif hasattr(o, '__iter__'):
+ return [i for i in o]
+ return super(JSONEncoder, self).default(o)
+
+
+try:
+ import yaml
+except ImportError:
+ SafeDumper = None
+else:
+ # Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py
+ class SafeDumper(yaml.SafeDumper):
+ """
+ Handles decimals as strings.
+ Handles SortedDicts as usual dicts, but preserves field order, rather
+ than the usual behaviour of sorting the keys.
+ """
+ def represent_decimal(self, data):
+ return self.represent_scalar('tag:yaml.org,2002:str', str(data))
+
+ def represent_mapping(self, tag, mapping, flow_style=None):
+ value = []
+ node = yaml.MappingNode(tag, value, flow_style=flow_style)
+ if self.alias_key is not None:
+ self.represented_objects[self.alias_key] = node
+ best_style = True
+ if hasattr(mapping, 'items'):
+ mapping = list(mapping.items())
+ if not isinstance(mapping, SortedDict):
+ mapping.sort()
+ for item_key, item_value in mapping:
+ node_key = self.represent_data(item_key)
+ node_value = self.represent_data(item_value)
+ if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
+ best_style = False
+ if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style):
+ best_style = False
+ value.append((node_key, node_value))
+ if flow_style is None:
+ if self.default_flow_style is not None:
+ node.flow_style = self.default_flow_style
+ else:
+ node.flow_style = best_style
+ return node
+
+ SafeDumper.add_representer(SortedDict,
+ yaml.representer.SafeRepresenter.represent_dict)
+ SafeDumper.add_representer(DictWithMetadata,
+ yaml.representer.SafeRepresenter.represent_dict)
+ SafeDumper.add_representer(SortedDictWithMetadata,
+ yaml.representer.SafeRepresenter.represent_dict)
+ SafeDumper.add_representer(types.GeneratorType,
+ yaml.representer.SafeRepresenter.represent_list)
diff --git a/djangorestframework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py
index 48dca0f0..ee7f3a54 100644
--- a/djangorestframework/utils/mediatypes.py
+++ b/rest_framework/utils/mediatypes.py
@@ -25,35 +25,9 @@ def media_type_matches(lhs, rhs):
return lhs.match(rhs)
-def is_form_media_type(media_type):
- """
- Return True if the media type is a valid form media type as defined by the HTML4 spec.
- (NB. HTML5 also adds text/plain to the list of valid form media types, but we don't support this here)
- """
- media_type = _MediaType(media_type)
- return media_type.full_type == 'application/x-www-form-urlencoded' or \
- media_type.full_type == 'multipart/form-data'
-
-
-def add_media_type_param(media_type, key, val):
- """
- Add a key, value parameter to a media type string, and return the new media type string.
- """
- media_type = _MediaType(media_type)
- media_type.params[key] = val
- return str(media_type)
-
-
-def get_media_type_params(media_type):
- """
- Return a dictionary of the parameters on the given media type.
- """
- return _MediaType(media_type).params
-
-
def order_by_precedence(media_type_lst):
"""
- Returns a list of lists of media type strings, ordered by precedence.
+ Returns a list of sets of media type strings, ordered by precedence.
Precedence is determined by how specific a media type is:
3. 'type/subtype; param=val'
@@ -61,11 +35,11 @@ def order_by_precedence(media_type_lst):
1. 'type/*'
0. '*/*'
"""
- ret = [[], [], [], []]
+ ret = [set(), set(), set(), set()]
for media_type in media_type_lst:
precedence = _MediaType(media_type).precedence
- ret[3 - precedence].append(media_type)
- return ret
+ ret[3 - precedence].add(media_type)
+ return [media_types for media_types in ret if media_types]
class _MediaType(object):
diff --git a/rest_framework/views.py b/rest_framework/views.py
new file mode 100644
index 00000000..71e1fe6c
--- /dev/null
+++ b/rest_framework/views.py
@@ -0,0 +1,370 @@
+"""
+Provides an APIView class that is used as the base of all class-based views.
+"""
+
+import re
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+from django.views.decorators.csrf import csrf_exempt
+from rest_framework import status, exceptions
+from rest_framework.compat import View, apply_markdown
+from rest_framework.response import Response
+from rest_framework.request import Request
+from rest_framework.settings import api_settings
+
+
+def _remove_trailing_string(content, trailing):
+ """
+ Strip trailing component `trailing` from `content` if it exists.
+ Used when generating names from view classes.
+ """
+ if content.endswith(trailing) and content != trailing:
+ return content[:-len(trailing)]
+ return content
+
+
+def _remove_leading_indent(content):
+ """
+ Remove leading indent from a block of text.
+ Used when generating descriptions from docstrings.
+ """
+ whitespace_counts = [len(line) - len(line.lstrip(' '))
+ for line in content.splitlines()[1:] if line.lstrip()]
+
+ # unindent the content if needed
+ if whitespace_counts:
+ whitespace_pattern = '^' + (' ' * min(whitespace_counts))
+ content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
+ content = content.strip('\n')
+ return content
+
+
+def _camelcase_to_spaces(content):
+ """
+ Translate 'CamelCaseNames' to 'Camel Case Names'.
+ Used when generating names from view classes.
+ """
+ camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
+ content = re.sub(camelcase_boundry, ' \\1', content).strip()
+ return ' '.join(content.split('_')).title()
+
+
+class APIView(View):
+ settings = api_settings
+
+ renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
+ parser_classes = api_settings.DEFAULT_PARSER_CLASSES
+ authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
+ throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
+ permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
+ content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
+
+ @classmethod
+ def as_view(cls, **initkwargs):
+ """
+ Override the default :meth:`as_view` to store an instance of the view
+ as an attribute on the callable function. This allows us to discover
+ information about the view when we do URL reverse lookups.
+ """
+ # TODO: deprecate?
+ view = super(APIView, cls).as_view(**initkwargs)
+ view.cls_instance = cls(**initkwargs)
+ return view
+
+ @property
+ def allowed_methods(self):
+ """
+ Return the list of allowed HTTP methods, uppercased.
+ """
+ return [method.upper() for method in self.http_method_names
+ if hasattr(self, method)]
+
+ @property
+ def default_response_headers(self):
+ # TODO: deprecate?
+ # TODO: Only vary by accept if multiple renderers
+ return {
+ 'Allow': ', '.join(self.allowed_methods),
+ 'Vary': 'Accept'
+ }
+
+ def get_name(self):
+ """
+ Return the resource or view class name for use as this view's name.
+ Override to customize.
+ """
+ # TODO: deprecate?
+ name = self.__class__.__name__
+ name = _remove_trailing_string(name, 'View')
+ return _camelcase_to_spaces(name)
+
+ def get_description(self, html=False):
+ """
+ Return the resource or view docstring for use as this view's description.
+ Override to customize.
+ """
+ # TODO: deprecate?
+ description = self.__doc__ or ''
+ description = _remove_leading_indent(description)
+ if html:
+ return self.markup_description(description)
+ return description
+
+ def markup_description(self, description):
+ """
+ Apply HTML markup to the description of this view.
+ """
+ # TODO: deprecate?
+ if apply_markdown:
+ description = apply_markdown(description)
+ else:
+ description = escape(description).replace('\n', '<br />')
+ return mark_safe(description)
+
+ def metadata(self, request):
+ return {
+ 'name': self.get_name(),
+ 'description': self.get_description(),
+ 'renders': [renderer.media_type for renderer in self.renderer_classes],
+ 'parses': [parser.media_type for parser in self.parser_classes],
+ }
+ # TODO: Add 'fields', from serializer info, if it exists.
+ # serializer = self.get_serializer()
+ # if serializer is not None:
+ # field_name_types = {}
+ # for name, field in form.fields.iteritems():
+ # field_name_types[name] = field.__class__.__name__
+ # content['fields'] = field_name_types
+
+ def http_method_not_allowed(self, request, *args, **kwargs):
+ """
+ Called if `request.method` does not corrospond to a handler method.
+ """
+ raise exceptions.MethodNotAllowed(request.method)
+
+ def permission_denied(self, request):
+ """
+ If request is not permitted, determine what kind of exception to raise.
+ """
+ raise exceptions.PermissionDenied()
+
+ def throttled(self, request, wait):
+ """
+ If request is throttled, determine what kind of exception to raise.
+ """
+ raise exceptions.Throttled(wait)
+
+ def get_parser_context(self, http_request):
+ """
+ Returns a dict that is passed through to Parser.parse(),
+ as the `parser_context` keyword argument.
+ """
+ # Note: Additionally `request` will also be added to the context
+ # by the Request object.
+ return {
+ 'view': self,
+ 'args': getattr(self, 'args', ()),
+ 'kwargs': getattr(self, 'kwargs', {})
+ }
+
+ def get_renderer_context(self):
+ """
+ Returns a dict that is passed through to Renderer.render(),
+ as the `renderer_context` keyword argument.
+ """
+ # Note: Additionally 'response' will also be added to the context,
+ # by the Response object.
+ return {
+ 'view': self,
+ 'args': getattr(self, 'args', ()),
+ 'kwargs': getattr(self, 'kwargs', {}),
+ 'request': getattr(self, 'request', None)
+ }
+
+ # API policy instantiation methods
+
+ def get_format_suffix(self, **kwargs):
+ """
+ Determine if the request includes a '.json' style format suffix
+ """
+ if self.settings.FORMAT_SUFFIX_KWARG:
+ return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG)
+
+ def get_renderers(self):
+ """
+ Instantiates and returns the list of renderers that this view can use.
+ """
+ return [renderer() for renderer in self.renderer_classes]
+
+ def get_parsers(self):
+ """
+ Instantiates and returns the list of renderers that this view can use.
+ """
+ return [parser() for parser in self.parser_classes]
+
+ def get_authenticators(self):
+ """
+ Instantiates and returns the list of renderers that this view can use.
+ """
+ return [auth() for auth in self.authentication_classes]
+
+ def get_permissions(self):
+ """
+ Instantiates and returns the list of permissions that this view requires.
+ """
+ return [permission() for permission in self.permission_classes]
+
+ def get_throttles(self):
+ """
+ Instantiates and returns the list of throttles that this view uses.
+ """
+ return [throttle() for throttle in self.throttle_classes]
+
+ def get_content_negotiator(self):
+ """
+ Instantiate and return the content negotiation class to use.
+ """
+ if not getattr(self, '_negotiator', None):
+ self._negotiator = self.content_negotiation_class()
+ return self._negotiator
+
+ # API policy implementation methods
+
+ def perform_content_negotiation(self, request, force=False):
+ """
+ Determine which renderer and media type to use render the response.
+ """
+ renderers = self.get_renderers()
+ conneg = self.get_content_negotiator()
+
+ try:
+ return conneg.select_renderer(request, renderers, self.format_kwarg)
+ except:
+ if force:
+ return (renderers[0], renderers[0].media_type)
+ raise
+
+ def has_permission(self, request, obj=None):
+ """
+ Return `True` if the request should be permitted.
+ """
+ for permission in self.get_permissions():
+ if not permission.has_permission(request, self, obj):
+ return False
+ return True
+
+ def check_throttles(self, request):
+ """
+ Check if request should be throttled.
+ """
+ for throttle in self.get_throttles():
+ if not throttle.allow_request(request, self):
+ self.throttled(request, throttle.wait())
+
+ # Dispatch methods
+
+ def initialize_request(self, request, *args, **kargs):
+ """
+ Returns the initial request object.
+ """
+ parser_context = self.get_parser_context(request)
+
+ return Request(request,
+ parsers=self.get_parsers(),
+ authenticators=self.get_authenticators(),
+ negotiator=self.get_content_negotiator(),
+ parser_context=parser_context)
+
+ def initial(self, request, *args, **kwargs):
+ """
+ Runs anything that needs to occur prior to calling the method handler.
+ """
+ self.format_kwarg = self.get_format_suffix(**kwargs)
+
+ # Ensure that the incoming request is permitted
+ if not self.has_permission(request):
+ self.permission_denied(request)
+ self.check_throttles(request)
+
+ # Perform content negotiation and store the accepted info on the request
+ neg = self.perform_content_negotiation(request)
+ request.accepted_renderer, request.accepted_media_type = neg
+
+ def finalize_response(self, request, response, *args, **kwargs):
+ """
+ Returns the final response object.
+ """
+ if isinstance(response, Response):
+ if not getattr(request, 'accepted_renderer', None):
+ neg = self.perform_content_negotiation(request, force=True)
+ request.accepted_renderer, request.accepted_media_type = neg
+
+ response.accepted_renderer = request.accepted_renderer
+ response.accepted_media_type = request.accepted_media_type
+ response.renderer_context = self.get_renderer_context()
+
+ for key, value in self.headers.items():
+ response[key] = value
+
+ return response
+
+ def handle_exception(self, exc):
+ """
+ Handle any exception that occurs, by returning an appropriate response,
+ or re-raising the error.
+ """
+ if isinstance(exc, exceptions.Throttled):
+ # Throttle wait header
+ self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait
+
+ if isinstance(exc, exceptions.APIException):
+ return Response({'detail': exc.detail}, status=exc.status_code)
+ elif isinstance(exc, Http404):
+ return Response({'detail': 'Not found'},
+ status=status.HTTP_404_NOT_FOUND)
+ elif isinstance(exc, PermissionDenied):
+ return Response({'detail': 'Permission denied'},
+ status=status.HTTP_403_FORBIDDEN)
+ raise
+
+ # Note: session based authentication is explicitly CSRF validated,
+ # all other authentication is CSRF exempt.
+ @csrf_exempt
+ def dispatch(self, request, *args, **kwargs):
+ """
+ `.dispatch()` is pretty much the same as Django's regular dispatch,
+ but with extra hooks for startup, finalize, and exception handling.
+ """
+ request = self.initialize_request(request, *args, **kwargs)
+ self.request = request
+ self.args = args
+ self.kwargs = kwargs
+ self.headers = self.default_response_headers # deprecate?
+
+ try:
+ self.initial(request, *args, **kwargs)
+
+ # Get the appropriate handler method
+ if request.method.lower() in self.http_method_names:
+ handler = getattr(self, request.method.lower(),
+ self.http_method_not_allowed)
+ else:
+ handler = self.http_method_not_allowed
+
+ response = handler(request, *args, **kwargs)
+
+ except Exception as exc:
+ response = self.handle_exception(exc)
+
+ self.response = self.finalize_response(request, response, *args, **kwargs)
+ return self.response
+
+ def options(self, request, *args, **kwargs):
+ """
+ Handler method for HTTP 'OPTIONS' request.
+ We may as well implement this as Django will otherwise provide
+ a less useful default implementation.
+ """
+ return Response(self.metadata(request), status=status.HTTP_200_OK)
diff --git a/setup.py b/setup.py
index 79bd7353..8ac2f03f 100755
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ def get_package_data(package):
return {package: filepaths}
-version = get_version('djangorestframework')
+version = get_version('rest_framework')
if sys.argv[-1] == 'publish':
@@ -52,21 +52,18 @@ if sys.argv[-1] == 'publish':
setup(
- name='djangorestframework',
+ name='rest_framework',
version=version,
url='http://django-rest-framework.org',
- download_url='http://pypi.python.org/pypi/djangorestframework/',
+ download_url='http://pypi.python.org/pypi/rest_framework/',
license='BSD',
description='A lightweight REST framework for Django.',
author='Tom Christie',
author_email='tom@tomchristie.com',
- packages=get_packages('djangorestframework'),
- package_data=get_package_data('djangorestframework'),
- test_suite='djangorestframework.runtests.runcoverage.main',
- install_requires=['URLObject>=0.6.0'],
- extras_require={
- 'markdown': ["Markdown>=2.0"]
- },
+ packages=get_packages('rest_framework'),
+ package_data=get_package_data('rest_framework'),
+ test_suite='rest_framework.runtests.runtests.main',
+ install_requires=[],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
diff --git a/tox.ini b/tox.ini
index 8c3af256..bcfff672 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,283 +1,30 @@
-#This file is very NON-DRY because tox currently doesn't support interpolation like configparser does.
-#There's a ticket covering this at http://code.google.com/p/pytox/issues/detail?id=17#c0
-
[tox]
-envlist=
- py25-django12,
- py26-django12,
- py27-django12,
- py25-django13,
- py26-django13,
- py27-django13,
- py25-django14a1,
- py26-django14a1,
- py27-django14a1,
- py25-django12-examples,
- py26-django12-examples,
- py27-django12-examples,
- py25-django13-examples,
- py26-django13-examples,
- py27-django13-examples,
- py25-django14a1-examples,
- py26-django14a1-examples,
- py27-django14a1-examples
-
-########################################### CORE TESTS ############################################
+downloadcache = {toxworkdir}/cache/
+envlist = py2.7-django1.5,py2.7-django1.4,py2.7-django1.3,py2.6-django1.5,py2.6-django1.4,py2.6-django1.3
[testenv]
-commands=
- python setup.py test
-
-[testenv:py25-django12]
-basepython=python2.5
-deps=
- django==1.2.4
- django-staticfiles>=1.1.2
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
-
-[testenv:py26-django12]
-basepython=python2.6
-deps=
- django==1.2.4
- django-staticfiles>=1.1.2
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
-
-[testenv:py27-django12]
-basepython=python2.7
-deps=
- django==1.2.4
- django-staticfiles>=1.1.2
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
-
-[testenv:py25-django13]
-basepython=python2.5
-deps=
- django==1.3
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
-
-[testenv:py26-django13]
-basepython=python2.6
-deps=
- django==1.3
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
-
-[testenv:py27-django13]
-basepython=python2.7
-deps=
- django==1.3
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
-
-[testenv:py25-django14a1]
-basepython=python2.5
-deps=
- http://www.djangoproject.com/download/1.4-alpha-1/tarball/
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
+commands = {envpython} rest_framework/runtests/runtests.py
-[testenv:py26-django14a1]
-basepython=python2.6
-deps=
- http://www.djangoproject.com/download/1.4-alpha-1/tarball/
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
+[testenv:py2.7-django1.5]
+basepython = python2.7
+deps = https://github.com/django/django/zipball/master
-[testenv:py27-django14a1]
-basepython=python2.7
-deps=
- http://www.djangoproject.com/download/1.4-alpha-1/tarball/
- coverage==3.4
- URLObject>=0.6.0
- unittest-xml-reporting==1.2
- Pyyaml==3.10
- # Optional packages:
- markdown
+[testenv:py2.7-django1.4]
+basepython = python2.7
+deps = django==1.4.1
-####################################### EXAMPLES ################################################
-
-[testenv:py25-django12-examples]
-basepython=python2.5
-commands=
- python examples/runtests.py
-deps=
- django==1.2.4
- django-staticfiles>=1.1.2
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py26-django12-examples]
-basepython=python2.6
-commands=
- python examples/runtests.py
-deps=
- django==1.2.4
- django-staticfiles>=1.1.2
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py27-django12-examples]
-basepython=python2.7
-commands=
- python examples/runtests.py
-deps=
- django==1.2.4
- django-staticfiles>=1.1.2
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py25-django13-examples]
-basepython=python2.5
-commands=
- python examples/runtests.py
-deps=
- django==1.3
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py26-django13-examples]
-basepython=python2.6
-commands=
- python examples/runtests.py
-deps=
- django==1.3
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py27-django13-examples]
-basepython=python2.7
-commands=
- python examples/runtests.py
-deps=
- django==1.3
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
+[testenv:py2.7-django1.3]
+basepython = python2.7
+deps = django==1.3.3
-[testenv:py25-django14a1-examples]
-basepython=python2.5
-commands=
- python examples/runtests.py
-deps=
- http://www.djangoproject.com/download/1.4-alpha-1/tarball/
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py26-django14a1-examples]
-basepython=python2.6
-commands=
- python examples/runtests.py
-deps=
- http://www.djangoproject.com/download/1.4-alpha-1/tarball/
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
-
-[testenv:py27-django14a1-examples]
-basepython=python2.7
-commands=
- python examples/runtests.py
-deps=
- http://www.djangoproject.com/download/1.4-alpha-1/tarball/
- coverage==3.4
- URLObject>=0.6.0
- wsgiref==0.1.2
- Pygments==1.4
- httplib2==0.6.0
- Markdown==2.0.3
- unittest-xml-reporting==1.2
- Pyyaml==3.10
+[testenv:py2.6-django1.5]
+basepython = python2.6
+deps = https://github.com/django/django/zipball/master
-##########################################DOCS#################################################
+[testenv:py2.6-django1.4]
+basepython = python2.6
+deps = django==1.4.1
-[testenv:docs]
-basepython=python
-changedir=docs
-deps=
- sphinx
- pytest
- django==1.3
-commands=
- py.test --tb=line -v --junitxml=junit-{envname}.xml check_sphinx.py
+[testenv:py2.6-django1.3]
+basepython = python2.6
+deps = django==1.3.3