From 714a90d7559885c15e5b2c86ef6f457fdf857ee0 Mon Sep 17 00:00:00 2001 From: Sébastien Piquemal Date: Tue, 24 Jan 2012 21:21:10 +0200 Subject: documentation for request module --- docs/library/request.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/library/request.rst (limited to 'docs') diff --git a/docs/library/request.rst b/docs/library/request.rst new file mode 100644 index 00000000..5e99826a --- /dev/null +++ b/docs/library/request.rst @@ -0,0 +1,5 @@ +:mod:`request` +===================== + +.. automodule:: request + :members: -- cgit v1.2.3 From 152c385f4de37558fe4e522abad5b97f0cf7ddce Mon Sep 17 00:00:00 2001 From: Sébastien Piquemal Date: Wed, 25 Jan 2012 00:11:54 +0200 Subject: enhanced request how-to + example --- docs/howto/requestmixin.rst | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/howto/requestmixin.rst (limited to 'docs') diff --git a/docs/howto/requestmixin.rst b/docs/howto/requestmixin.rst new file mode 100644 index 00000000..a00fdad0 --- /dev/null +++ b/docs/howto/requestmixin.rst @@ -0,0 +1,76 @@ +Using the enhanced request in all your views +============================================== + +This example shows how you can use Django REST framework's enhanced `request` in your own views, without having to use the full-blown :class:`views.View` class. + +What can it do for you ? Mostly, it will take care of parsing the request's content, and handling equally all HTTP methods ... + +Before +-------- + +In order to support `JSON` or other serial formats, you might have parsed manually the request's content with something like : :: + + class MyView(View): + + def put(self, request, *args, **kwargs): + content_type = request.META['CONTENT_TYPE'] + if (content_type == 'application/json'): + raw_data = request.read() + parsed_data = json.loads(raw_data) + + # PLUS as many `elif` as formats you wish to support ... + + # then do stuff with your data : + self.do_stuff(parsed_data['bla'], parsed_data['hoho']) + + # and finally respond something + +... and you were unhappy because this looks hackish. + +Also, you might have tried uploading files with a PUT request - *and given up* since that's complicated to achieve even with Django 1.3. + + +After +------ + +All the dirty `Content-type` checking and content reading and parsing is done for you, and you only need to do the following : :: + + class MyView(MyBaseViewUsingEnhancedRequest): + + def put(self, request, *args, **kwargs): + self.do_stuff(request.DATA['bla'], request.DATA['hoho']) + # and finally respond something + +So the parsed content is magically available as `.DATA` on the `request` object. + +Also, if you uploaded files, they are available as `.FILES`, like with a normal POST request. + +.. note:: Note that all the above is also valid for a POST request. + + +How to add it to your custom views ? +-------------------------------------- + +Now that you're convinced you need to use the enhanced request object, here is how you can add it to all your custom views : :: + + from django.views.generic.base import View + + from djangorestframework.mixins import RequestMixin + from djangorestframework import parsers + + + class MyBaseViewUsingEnhancedRequest(RequestMixin, View): + """ + Base view enabling the usage of enhanced requests with user defined views. + """ + + parsers = parsers.DEFAULT_PARSERS + + def dispatch(self, request, *args, **kwargs): + self.request = request + request = self.get_request() + return super(MyBaseViewUsingEnhancedRequest, self).dispatch(request, *args, **kwargs) + +And then, use this class as a base for all your custom views. + +.. note:: you can also check the request example. -- cgit v1.2.3 From 6963fd3623ee217fe489abb25f0ffa8c0781e4cd Mon Sep 17 00:00:00 2001 From: Sébastien Piquemal Date: Tue, 7 Feb 2012 16:22:14 +0200 Subject: some docs for Request/Response/mixins --- docs/howto/requestmixin.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/howto/requestmixin.rst b/docs/howto/requestmixin.rst index a00fdad0..c0aadb3f 100644 --- a/docs/howto/requestmixin.rst +++ b/docs/howto/requestmixin.rst @@ -1,7 +1,7 @@ Using the enhanced request in all your views ============================================== -This example shows how you can use Django REST framework's enhanced `request` in your own views, without having to use the full-blown :class:`views.View` class. +This example shows how you can use Django REST framework's enhanced `request` - :class:`request.Request` - in your own views, without having to use the full-blown :class:`views.View` class. What can it do for you ? Mostly, it will take care of parsing the request's content, and handling equally all HTTP methods ... @@ -64,13 +64,12 @@ Now that you're convinced you need to use the enhanced request object, here is h Base view enabling the usage of enhanced requests with user defined views. """ - parsers = parsers.DEFAULT_PARSERS + parser_classes = parsers.DEFAULT_PARSERS def dispatch(self, request, *args, **kwargs): - self.request = request - request = self.get_request() + request = self.prepare_request(request) return super(MyBaseViewUsingEnhancedRequest, self).dispatch(request, *args, **kwargs) And then, use this class as a base for all your custom views. -.. note:: you can also check the request example. +.. note:: you can see this live in the examples. -- cgit v1.2.3 From af9e4f69d732cc643d6ec7ae13d4a19ac0332d44 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 21 Feb 2012 20:12:14 +0000 Subject: Merging master into develop --- docs/howto/reverse.rst | 47 ++++++++++++++++++++++++++++++++++++ docs/howto/setup.rst | 65 ++++++++++++++++++++++++++++++++------------------ docs/index.rst | 14 +++++++---- docs/library/utils.rst | 5 ++++ 4 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 docs/howto/reverse.rst create mode 100644 docs/library/utils.rst (limited to 'docs') diff --git a/docs/howto/reverse.rst b/docs/howto/reverse.rst new file mode 100644 index 00000000..e4efbbca --- /dev/null +++ b/docs/howto/reverse.rst @@ -0,0 +1,47 @@ +Returning URIs from your Web APIs +================================= + + "The central feature that distinguishes the REST architectural style from + other network-based styles is its emphasis on a uniform interface between + components." + + -- Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures + +As a rule, it's probably better practice to return absolute URIs from you web APIs, e.g. "http://example.com/foobar", rather than returning relative URIs, e.g. "/foobar". + +The advantages of doing so are: + +* It's more explicit. +* It leaves less work for your API clients. +* There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type. +* It allows us to easily do things like markup HTML representations with hyperlinks. + +Django REST framework provides two utility functions to make it simpler to return absolute URIs from your Web API. + +There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink its output for you, which makes browsing the API much easier. + +reverse(viewname, request, ...) +------------------------------- + +The :py:func:`~utils.reverse` function has the same behavior as :py:func:`django.core.urlresolvers.reverse` [1]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port:: + + from djangorestframework.utils import reverse + from djangorestframework.views import View + + class MyView(View): + def get(self, request): + context = { + 'url': reverse('year-summary', request, args=[1945]) + } + + return Response(context) + +reverse_lazy(viewname, request, ...) +------------------------------------ + +The :py:func:`~utils.reverse_lazy` function has the same behavior as :py:func:`django.core.urlresolvers.reverse_lazy` [2]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port. + +.. rubric:: Footnotes + +.. [1] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse +.. [2] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy diff --git a/docs/howto/setup.rst b/docs/howto/setup.rst index 22f98f0c..0af1449c 100644 --- a/docs/howto/setup.rst +++ b/docs/howto/setup.rst @@ -3,45 +3,58 @@ Setup ===== -Installing into site-packages ------------------------------ +Templates +--------- -If you need to manually install Django REST framework to your ``site-packages`` directory, run the ``setup.py`` script:: +Django REST framework uses a few templates for the HTML and plain text +documenting renderers. You'll need to ensure ``TEMPLATE_LOADERS`` setting +contains ``'django.template.loaders.app_directories.Loader'``. +This will already be the case by default. - python setup.py install +You may customize the templates by creating a new template called +``djangorestframework/api.html`` in your project, which should extend +``djangorestframework/base.html`` and override the appropriate +block tags. For example:: -Template Loaders ----------------- + {% extends "djangorestframework/base.html" %} -Django REST framework uses a few templates for the HTML and plain text documenting renderers. + {% block title %}My API{% endblock %} -* Ensure ``TEMPLATE_LOADERS`` setting contains ``'django.template.loaders.app_directories.Loader'``. + {% block branding %} +

My API

+ {% endblock %} -This will be the case by default so you shouldn't normally need to do anything here. -Admin Styling -------------- +Styling +------- -Django REST framework uses the admin media for styling. When running using Django's testserver this is automatically served for you, -but once you move onto a production server, you'll want to make sure you serve the admin media separately, exactly as you would do -`if using the Django admin `_. +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`` -* Ensure that the ``ADMIN_MEDIA_PREFIX`` is set appropriately and that you are serving the admin media. - (Django's testserver will automatically serve the admin media for you) Markdown -------- -The Python `markdown library `_ is not required but comes recommended. +`Python markdown`_ is not required but comes recommended. + +If markdown is installed your :class:`.Resource` descriptions can include +`markdown formatting`_ which will be rendered by the self-documenting API. + +YAML +---- + +YAML support is optional, and requires `PyYAML`_. -If markdown is installed your :class:`.Resource` descriptions can include `markdown style formatting -`_ which will be rendered by the HTML documenting renderer. -login/logout ---------------------------------- +Login / Logout +-------------- -Django REST framework comes with a few views that can be useful including an api -login and logout views:: +Django REST framework includes login and logout views that are useful if +you're using the self-documenting API:: from django.conf.urls.defaults import patterns @@ -51,3 +64,9 @@ login and logout views:: (r'^accounts/logout/$', 'api_logout'), ) +.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ +.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/ +.. _URLObject: http://pypi.python.org/pypi/URLObject/ +.. _Python markdown: http://www.freewisdom.org/projects/python-markdown/ +.. _markdown formatting: http://daringfireball.net/projects/markdown/syntax +.. _PyYAML: http://pypi.python.org/pypi/PyYAML \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index ecc1f118..b969c4a3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,8 +40,11 @@ Requirements ------------ * Python (2.5, 2.6, 2.7 supported) -* Django (1.2, 1.3, 1.4-alpha supported) - +* Django (1.2, 1.3, 1.4 supported) +* `django.contrib.staticfiles`_ (or `django-staticfiles`_ for Django 1.2) +* `URLObject`_ >= 2.0.0 +* `Markdown`_ >= 2.1.0 (Optional) +* `PyYAML`_ >= 3.10 (Optional) Installation ------------ @@ -54,8 +57,6 @@ Or get the latest development version using git:: git clone git@github.com:tomchristie/django-rest-framework.git -Or you can `download the current release `_. - Setup ----- @@ -114,3 +115,8 @@ Indices and tables * :ref:`modindex` * :ref:`search` +.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ +.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/ +.. _URLObject: http://pypi.python.org/pypi/URLObject/ +.. _Markdown: http://pypi.python.org/pypi/Markdown/ +.. _PyYAML: http://pypi.python.org/pypi/PyYAML diff --git a/docs/library/utils.rst b/docs/library/utils.rst new file mode 100644 index 00000000..653f24fd --- /dev/null +++ b/docs/library/utils.rst @@ -0,0 +1,5 @@ +:mod:`utils` +============== + +.. automodule:: utils + :members: -- cgit v1.2.3 From 5fd4c639d7c64572dd07dc31dcd627bed9469b05 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 21 Feb 2012 20:57:36 +0000 Subject: Merge master into develop --- docs/howto/reverse.rst | 18 +++++------------- docs/library/reverse.rst | 5 +++++ 2 files changed, 10 insertions(+), 13 deletions(-) create mode 100644 docs/library/reverse.rst (limited to 'docs') diff --git a/docs/howto/reverse.rst b/docs/howto/reverse.rst index e4efbbca..73b8fa4d 100644 --- a/docs/howto/reverse.rst +++ b/docs/howto/reverse.rst @@ -1,12 +1,6 @@ Returning URIs from your Web APIs ================================= - "The central feature that distinguishes the REST architectural style from - other network-based styles is its emphasis on a uniform interface between - components." - - -- Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures - As a rule, it's probably better practice to return absolute URIs from you web APIs, e.g. "http://example.com/foobar", rather than returning relative URIs, e.g. "/foobar". The advantages of doing so are: @@ -23,9 +17,9 @@ There's no requirement for you to use them, but if you do then the self-describi reverse(viewname, request, ...) ------------------------------- -The :py:func:`~utils.reverse` function has the same behavior as :py:func:`django.core.urlresolvers.reverse` [1]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port:: +The :py:func:`~reverse.reverse` function has the same behavior as `django.core.urlresolvers.reverse`_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port:: - from djangorestframework.utils import reverse + from djangorestframework.reverse import reverse from djangorestframework.views import View class MyView(View): @@ -39,9 +33,7 @@ The :py:func:`~utils.reverse` function has the same behavior as :py:func:`django reverse_lazy(viewname, request, ...) ------------------------------------ -The :py:func:`~utils.reverse_lazy` function has the same behavior as :py:func:`django.core.urlresolvers.reverse_lazy` [2]_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port. - -.. rubric:: Footnotes +The :py:func:`~reverse.reverse_lazy` function has the same behavior as `django.core.urlresolvers.reverse_lazy`_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port. -.. [1] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse -.. [2] https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy +.. _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/library/reverse.rst b/docs/library/reverse.rst new file mode 100644 index 00000000..a2c29c48 --- /dev/null +++ b/docs/library/reverse.rst @@ -0,0 +1,5 @@ +:mod:`reverse` +================ + +.. automodule:: reverse + :members: -- cgit v1.2.3 From 1cde31c86d9423e9b7a7409c2ef2ba7c0500e47f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 25 Feb 2012 18:45:17 +0000 Subject: Massive merge --- docs/howto/setup.rst | 18 +++++++++--------- docs/index.rst | 6 ++++++ 2 files changed, 15 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/howto/setup.rst b/docs/howto/setup.rst index 0af1449c..081c6412 100644 --- a/docs/howto/setup.rst +++ b/docs/howto/setup.rst @@ -49,20 +49,20 @@ YAML YAML support is optional, and requires `PyYAML`_. - Login / Logout -------------- -Django REST framework includes login and logout views that are useful if -you're using the self-documenting API:: +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 + from django.conf.urls.defaults import patterns, url - urlpatterns = patterns('djangorestframework.views', - # Add your resources here - (r'^accounts/login/$', 'api_login'), - (r'^accounts/logout/$', 'api_logout'), - ) + 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/ diff --git a/docs/index.rst b/docs/index.rst index b969c4a3..a6745fca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -64,6 +64,12 @@ To add Django REST framework to a Django project: * Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``. * Add ``djangorestframework`` to your ``INSTALLED_APPS``. +* Add the following to your URLconf. (To include the REST framework Login/Logout views.):: + + urlpatterns = patterns('', + ... + url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework')) + ) For more information on settings take a look at the :ref:`setup` section. -- cgit v1.2.3 From 578017e01d1da4746ae0045268043cfd74d41b42 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 29 Aug 2012 20:57:37 +0100 Subject: New docs --- docs/check_sphinx.py | 9 - docs/conf.py | 228 -------------------- docs/contents.rst | 10 - docs/csrf.md | 4 + docs/examples.rst | 23 -- docs/examples/blogpost.rst | 37 ---- docs/examples/modelviews.rst | 56 ----- docs/examples/objectstore.rst | 17 -- docs/examples/permissions.rst | 66 ------ docs/examples/pygments.rst | 89 -------- docs/examples/sandbox.rst | 12 -- docs/examples/views.rst | 56 ----- docs/formoverloading.md | 46 ++++ docs/howto.rst | 8 - docs/howto/alternativeframeworks.rst | 35 --- docs/howto/mixin.rst | 30 --- docs/howto/requestmixin.rst | 75 ------- docs/howto/reverse.rst | 39 ---- docs/howto/setup.rst | 73 ------- docs/howto/usingcurl.rst | 30 --- docs/howto/usingurllib2.rst | 39 ---- docs/index.md | 62 ++++++ docs/index.rst | 128 ----------- docs/library.rst | 8 - docs/library/authentication.rst | 5 - docs/library/compat.rst | 5 - docs/library/mixins.rst | 5 - docs/library/parsers.rst | 5 - docs/library/permissions.rst | 5 - docs/library/renderers.rst | 10 - docs/library/request.rst | 5 - docs/library/resource.rst | 5 - docs/library/response.rst | 5 - docs/library/reverse.rst | 5 - docs/library/serializer.rst | 5 - docs/library/status.rst | 5 - docs/library/utils.rst | 5 - docs/library/views.rst | 5 - docs/parsers.md | 5 + docs/renderers.md | 6 + docs/request.md | 76 +++++++ docs/requirements.txt | 8 - docs/response.md | 27 +++ docs/serializers.md | 47 ++++ docs/status.md | 17 ++ docs/templates/layout.html | 28 --- docs/tutorial/1-serialization.md | 236 +++++++++++++++++++++ docs/tutorial/2-requests-and-responses.md | 137 ++++++++++++ docs/tutorial/3-class-based-views.md | 137 ++++++++++++ .../4-authentication-permissions-and-throttling.md | 3 + .../5-relationships-and-hyperlinked-apis.md | 9 + docs/tutorial/6-resource-orientated-projects.md | 49 +++++ docs/urls.md | 42 ++++ docs/views.md | 43 ++++ 54 files changed, 946 insertions(+), 1179 deletions(-) delete mode 100644 docs/check_sphinx.py delete mode 100644 docs/conf.py delete mode 100644 docs/contents.rst create mode 100644 docs/csrf.md delete mode 100644 docs/examples.rst delete mode 100644 docs/examples/blogpost.rst delete mode 100644 docs/examples/modelviews.rst delete mode 100644 docs/examples/objectstore.rst delete mode 100644 docs/examples/permissions.rst delete mode 100644 docs/examples/pygments.rst delete mode 100644 docs/examples/sandbox.rst delete mode 100644 docs/examples/views.rst create mode 100644 docs/formoverloading.md delete mode 100644 docs/howto.rst delete mode 100644 docs/howto/alternativeframeworks.rst delete mode 100644 docs/howto/mixin.rst delete mode 100644 docs/howto/requestmixin.rst delete mode 100644 docs/howto/reverse.rst delete mode 100644 docs/howto/setup.rst delete mode 100644 docs/howto/usingcurl.rst delete mode 100644 docs/howto/usingurllib2.rst create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/library.rst delete mode 100644 docs/library/authentication.rst delete mode 100644 docs/library/compat.rst delete mode 100644 docs/library/mixins.rst delete mode 100644 docs/library/parsers.rst delete mode 100644 docs/library/permissions.rst delete mode 100644 docs/library/renderers.rst delete mode 100644 docs/library/request.rst delete mode 100644 docs/library/resource.rst delete mode 100644 docs/library/response.rst delete mode 100644 docs/library/reverse.rst delete mode 100644 docs/library/serializer.rst delete mode 100644 docs/library/status.rst delete mode 100644 docs/library/utils.rst delete mode 100644 docs/library/views.rst create mode 100644 docs/parsers.md create mode 100644 docs/renderers.md create mode 100644 docs/request.md delete mode 100644 docs/requirements.txt create mode 100644 docs/response.md create mode 100644 docs/serializers.md create mode 100644 docs/status.md delete mode 100644 docs/templates/layout.html create mode 100644 docs/tutorial/1-serialization.md create mode 100644 docs/tutorial/2-requests-and-responses.md create mode 100644 docs/tutorial/3-class-based-views.md create mode 100644 docs/tutorial/4-authentication-permissions-and-throttling.md create mode 100644 docs/tutorial/5-relationships-and-hyperlinked-apis.md create mode 100644 docs/tutorial/6-resource-orientated-projects.md create mode 100644 docs/urls.md create mode 100644 docs/views.md (limited to 'docs') 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 -# " v documentation". -html_title = "Django REST framework" - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -#htmlhelp_basename = '' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -#latex_documents = [ -# (), -#] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -#man_pages = [ -# () -#] - -linkcheck_timeout = 120 # seconds, set to extra large value for link_checks diff --git a/docs/contents.rst b/docs/contents.rst deleted file mode 100644 index d8e6e742..00000000 --- a/docs/contents.rst +++ /dev/null @@ -1,10 +0,0 @@ -Documentation -============= - -.. toctree:: - :maxdepth: 2 - - howto - library - examples - diff --git a/docs/csrf.md b/docs/csrf.md new file mode 100644 index 00000000..8e0b9480 --- /dev/null +++ b/docs/csrf.md @@ -0,0 +1,4 @@ +REST framework and CSRF protection +================================== + +> "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability -- very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one." - Jeff Atwood \ No newline at end of file diff --git a/docs/examples.rst b/docs/examples.rst deleted file mode 100644 index 64088345..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://rest.ep.io - -(The :doc:`examples/sandbox` resource is also documented.) - -Example Reference ------------------ - -.. toctree:: - :maxdepth: 1 - :glob: - - examples/* diff --git a/docs/examples/blogpost.rst b/docs/examples/blogpost.rst deleted file mode 100644 index 11e376ef..00000000 --- a/docs/examples/blogpost.rst +++ /dev/null @@ -1,37 +0,0 @@ -Blog Posts API -============== - -* http://rest.ep.io/blog-post/ - -The models ----------- - -In this example we're working from two related models: - -``models.py`` - -.. include:: ../../examples/blogpost/models.py - :literal: - -Creating the resources ----------------------- - -We need to create two resources that we map to our two existing models, in order to describe how the models should be serialized. -Our resource descriptions will typically go into a module called something like 'resources.py' - -``resources.py`` - -.. include:: ../../examples/blogpost/resources.py - :literal: - -Creating views for our resources --------------------------------- - -Once we've created the resources there's very little we need to do to create the API. -For each resource we'll create a base view, and an instance view. -The generic views :class:`.ListOrCreateModelView` and :class:`.InstanceModelView` provide default operations for listing, creating and updating our models via the API, and also automatically provide input validation using default ModelForms for each model. - -``urls.py`` - -.. include:: ../../examples/blogpost/urls.py - :literal: diff --git a/docs/examples/modelviews.rst b/docs/examples/modelviews.rst deleted file mode 100644 index b67d50d9..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://rest.ep.io/model-resource-example/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/resource-example/ -H 'Accept: text/plain' - -Often you'll want parts of your API to directly map to existing django models. Django REST framework handles this nicely for you in a couple of ways: - -#. It automatically provides suitable create/read/update/delete methods for your views. -#. Input validation occurs automatically, by using appropriate `ModelForms `_. - -Here's the model we're working from in this example: - -``models.py`` - -.. include:: ../../examples/modelresourceexample/models.py - :literal: - -To add an API for the model, first we need to create a Resource for the model. - -``resources.py`` - -.. include:: ../../examples/modelresourceexample/resources.py - :literal: - -Then we simply map a couple of views to the Resource in our urlconf. - -``urls.py`` - -.. include:: ../../examples/modelresourceexample/urls.py - :literal: - -And we're done. We've now got a fully browseable API, which supports multiple input and output media types, and has all the nice automatic field validation that Django gives us for free. - -We can visit the API in our browser: - -* http://rest.ep.io/model-resource-example/ - -Or access it from the command line using curl: - -.. code-block:: bash - - # Demonstrates API's input validation using form input - bash: curl -X POST --data 'foo=true' http://rest.ep.io/model-resource-example/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} - - # Demonstrates API's input validation using JSON input - bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://rest.ep.io/model-resource-example/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} diff --git a/docs/examples/objectstore.rst b/docs/examples/objectstore.rst deleted file mode 100644 index 0939fe9c..00000000 --- a/docs/examples/objectstore.rst +++ /dev/null @@ -1,17 +0,0 @@ -Object Store API -================ - -* http://rest.ep.io/object-store/ - -This example shows an object store API that can be used to store arbitrary serializable content. - -``urls.py`` - -.. include:: ../../examples/objectstore/urls.py - :literal: - -``views.py`` - -.. include:: ../../examples/objectstore/views.py - :literal: - diff --git a/docs/examples/permissions.rst b/docs/examples/permissions.rst deleted file mode 100644 index eafc3255..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://rest.ep.io/permissions-example/loggedin - - -Throttling ----------- - -If you want to limit the amount of requests a client is allowed to do on -a resource, then you can set a 'throttle' to achieve this. - -For this to work you'll need to import the `PerUserThrottling` class from the `permissions` -module.:: - - from djangorestframework.permissions import PerUserThrottling - -In the example below we have limited the amount of requests one 'client' or 'user' -may do on our view to 10 requests per minute.: - -.. literalinclude:: ../../examples/permissionsexample/views.py - :pyobject: ThrottlingExampleView - -Try it yourself on the live sandbox__. - -__ http://rest.ep.io/permissions-example/throttling - -Now if you want a view to require both aurhentication and throttling, you simply declare them -both:: - - permissions = (PerUserThrottling, Isauthenticated) - -To see what other throttles are available, have a look at the :mod:`permissions` module. - -If you want to implement your own authentication method, then refer to the :mod:`authentication` -module. diff --git a/docs/examples/pygments.rst b/docs/examples/pygments.rst deleted file mode 100644 index 4e72f754..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 `_ library, to create the Web API for a simple pastebin. - -.. note:: - - A live sandbox instance of this API is available at http://rest.ep.io/pygments/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/pygments/ -H 'Accept: text/plain' - - -URL configuration ------------------ - -We'll need two resources: - -* A resource which represents the root of the API. -* A resource which represents an instance of a highlighted snippet. - -``urls.py`` - -.. include:: ../../examples/pygments_api/urls.py - :literal: - -Form validation ---------------- - -We'll now add a form to specify what input fields are required when creating a new highlighted code snippet. This will include: - -* The code text itself. -* An optional title for the code. -* A flag to determine if line numbers should be included. -* Which programming language to interpret the code snippet as. -* Which output style to use for the highlighting. - -``forms.py`` - -.. include:: ../../examples/pygments_api/forms.py - :literal: - -Creating the resources ----------------------- - -We'll need to define 3 resource handling methods on our resources. - -* ``PygmentsRoot.get()`` method, which lists all the existing snippets. -* ``PygmentsRoot.post()`` method, which creates new snippets. -* ``PygmentsInstance.get()`` method, which returns existing snippets. - -And set a number of attributes on our resources. - -* Set the ``allowed_methods`` and ``anon_allowed_methods`` attributes on both resources allowing for full unauthenticated access. -* Set the ``form`` attribute on the ``PygmentsRoot`` resource, to give us input validation when we create snippets. -* Set the ``emitters`` attribute on the ``PygmentsInstance`` resource, so that - -``views.py`` - -.. include:: ../../examples/pygments_api/views.py - :literal: - -Completed ---------- - -And we're done. We now have an API that is: - -* **Browseable.** The API supports media types for both programmatic and human access, and can be accessed either via a browser or from the command line. -* **Self describing.** The API serves as it's own documentation. -* **Well connected.** The API can be accessed fully by traversal from the initial URL. Clients never need to construct URLs themselves. - -Our API also supports multiple media types for both input and output, and applies sensible input validation in all cases. - -For example if we make a POST request using form input: - -.. code-block:: bash - - bash: curl -X POST --data 'code=print "hello, world!"' --data 'style=foobar' -H 'X-Requested-With: XMLHttpRequest' http://rest.ep.io/pygments/ - {"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}} - -Or if we make the same request using JSON: - -.. code-block:: bash - - bash: curl -X POST --data-binary '{"code":"print \"hello, world!\"", "style":"foobar"}' -H 'Content-Type: application/json' -H 'X-Requested-With: XMLHttpRequest' http://rest.ep.io/pygments/ - {"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}} - diff --git a/docs/examples/sandbox.rst b/docs/examples/sandbox.rst deleted file mode 100644 index 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 db0db0d7..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://rest.ep.io/resource-example/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/resource-example/ -H 'Accept: text/plain' - -We're going to start off with a simple example, that demonstrates a few things: - -#. Creating views. -#. Linking views. -#. Writing method handlers on views. -#. Adding form validation to views. - -First we'll define two views in our urlconf. - -``urls.py`` - -.. include:: ../../examples/resourceexample/urls.py - :literal: - -Now we'll add a form that we'll use for input validation. This is completely optional, but it's often useful. - -``forms.py`` - -.. include:: ../../examples/resourceexample/forms.py - :literal: - -Now we'll write our views. The first is a read only view that links to three instances of the second. The second view just has some stub handler methods to help us see that our example is working. - -``views.py`` - -.. include:: ../../examples/resourceexample/views.py - :literal: - -That's us done. Our API now provides both programmatic access using JSON and XML, as well a nice browseable HTML view, so we can now access it both from the browser: - -* http://rest.ep.io/resource-example/ - -And from the command line: - -.. code-block:: bash - - # Demonstrates API's input validation using form input - bash: curl -X POST --data 'foo=true' http://rest.ep.io/resource-example/1/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} - - # Demonstrates API's input validation using JSON input - bash: curl -X POST -H 'Content-Type: application/json' --data-binary '{"foo":true}' http://rest.ep.io/resource-example/1/ - {"detail": {"bar": ["This field is required."], "baz": ["This field is required."]}} diff --git a/docs/formoverloading.md b/docs/formoverloading.md new file mode 100644 index 00000000..cab47db9 --- /dev/null +++ b/docs/formoverloading.md @@ -0,0 +1,46 @@ +Supporting browser-based PUT & DELETE +===================================== + +> "There are two noncontroversial uses for overloaded POST. The first is to *simulate* HTTP's uniform interface for clients like web browsers that don't support PUT or DELETE" - [RESTful Web Services](1), Leonard Richardson & Sam Ruby. + +This is the same strategy as is used in [Ruby on Rails](2). + +Overloading the HTTP method +--------------------------- + +For example, given the following form: + +
+ +
+ +`request.method` would return `"DELETE"`. + +Overloading the HTTP content type +--------------------------------- + +Browser-based submission of content types other than form are supported by using form fields named `_content` and `_content_type`: + +For example, given the following form: + +
+ + +
+ +`request.content_type` would return `"application/json"`, and `request.content` would return `"{'count': 1}"` + +Why not just use Javascript? +============================ + +**[TODO]** + +Doesn't HTML5 support PUT and DELETE forms? +=========================================== + +Nope. It was at one point intended to support `PUT` and `DELETE` forms, but was later [dropped from the spec](3). There remains [ongoing discussion](4) about adding support for `PUT` and `DELETE`, as well as how to support content-types other than form-encoded data. + +[1]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260 +[2]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work +[3]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24 +[4]: http://amundsen.com/examples/put-delete-forms/ diff --git a/docs/howto.rst b/docs/howto.rst deleted file mode 100644 index 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 dc8d1ea6..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 `_ is very mature, and has a large community behind it. This project was originally based on piston code in parts. -* `django-tasypie `_ is also very good, and has a very active and helpful developer community and maintainers. -* Other interesting projects include `dagny `_ and `dj-webmachine `_ - - -Why use Django REST framework? ------------------------------- - -The big benefits of using Django REST framework come down to: - -1. It's based on Django's class based views, which makes it simple, modular, and future-proof. -2. It stays as close as possible to Django idioms and language throughout. -3. The browse-able API makes working with the APIs extremely quick and easy. - - -Why was this project created? ------------------------------ - -For me the browse-able API is the most important aspect of Django REST framework. - -I wanted to show that Web APIs could easily be made Web browse-able, -and demonstrate how much better browse-able Web APIs are to work with. - -Being able to navigate and use a Web API directly in the browser is a huge win over only having command line and programmatic -access to the API. It enables the API to be properly self-describing, and it makes it much much quicker and easier to work with. -There's no fundamental reason why the Web APIs we're creating shouldn't be able to render to HTML as well as JSON/XML/whatever, -and I really think that more Web API frameworks *in whatever language* ought to be taking a similar approach. diff --git a/docs/howto/mixin.rst b/docs/howto/mixin.rst deleted file mode 100644 index 1a84f2ad..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://rest.ep.io/mixin/ - - You can browse the API using a web browser, or from the command line:: - - curl -X GET http://rest.ep.io/mixin/ - - -URL configuration ------------------ - -Everything we need for this example can go straight into the URL conf... - -``urls.py`` - -.. include:: ../../examples/mixin/urls.py - :literal: - -That's it. Auto-magically our API now supports multiple output formats, specified either by using -standard HTTP Accept header content negotiation, or by using the `&_accept=application/json` style parameter overrides. -We even get a nice HTML view which can be used to self-document our API. diff --git a/docs/howto/requestmixin.rst b/docs/howto/requestmixin.rst deleted file mode 100644 index c0aadb3f..00000000 --- a/docs/howto/requestmixin.rst +++ /dev/null @@ -1,75 +0,0 @@ -Using the enhanced request in all your views -============================================== - -This example shows how you can use Django REST framework's enhanced `request` - :class:`request.Request` - in your own views, without having to use the full-blown :class:`views.View` class. - -What can it do for you ? Mostly, it will take care of parsing the request's content, and handling equally all HTTP methods ... - -Before --------- - -In order to support `JSON` or other serial formats, you might have parsed manually the request's content with something like : :: - - class MyView(View): - - def put(self, request, *args, **kwargs): - content_type = request.META['CONTENT_TYPE'] - if (content_type == 'application/json'): - raw_data = request.read() - parsed_data = json.loads(raw_data) - - # PLUS as many `elif` as formats you wish to support ... - - # then do stuff with your data : - self.do_stuff(parsed_data['bla'], parsed_data['hoho']) - - # and finally respond something - -... and you were unhappy because this looks hackish. - -Also, you might have tried uploading files with a PUT request - *and given up* since that's complicated to achieve even with Django 1.3. - - -After ------- - -All the dirty `Content-type` checking and content reading and parsing is done for you, and you only need to do the following : :: - - class MyView(MyBaseViewUsingEnhancedRequest): - - def put(self, request, *args, **kwargs): - self.do_stuff(request.DATA['bla'], request.DATA['hoho']) - # and finally respond something - -So the parsed content is magically available as `.DATA` on the `request` object. - -Also, if you uploaded files, they are available as `.FILES`, like with a normal POST request. - -.. note:: Note that all the above is also valid for a POST request. - - -How to add it to your custom views ? --------------------------------------- - -Now that you're convinced you need to use the enhanced request object, here is how you can add it to all your custom views : :: - - from django.views.generic.base import View - - from djangorestframework.mixins import RequestMixin - from djangorestframework import parsers - - - class MyBaseViewUsingEnhancedRequest(RequestMixin, View): - """ - Base view enabling the usage of enhanced requests with user defined views. - """ - - parser_classes = parsers.DEFAULT_PARSERS - - def dispatch(self, request, *args, **kwargs): - request = self.prepare_request(request) - return super(MyBaseViewUsingEnhancedRequest, self).dispatch(request, *args, **kwargs) - -And then, use this class as a base for all your custom views. - -.. note:: you can see this live in the examples. diff --git a/docs/howto/reverse.rst b/docs/howto/reverse.rst deleted file mode 100644 index 73b8fa4d..00000000 --- a/docs/howto/reverse.rst +++ /dev/null @@ -1,39 +0,0 @@ -Returning URIs from your Web APIs -================================= - -As a rule, it's probably better practice to return absolute URIs from you web APIs, e.g. "http://example.com/foobar", rather than returning relative URIs, e.g. "/foobar". - -The advantages of doing so are: - -* It's more explicit. -* It leaves less work for your API clients. -* There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type. -* It allows us to easily do things like markup HTML representations with hyperlinks. - -Django REST framework provides two utility functions to make it simpler to return absolute URIs from your Web API. - -There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink its output for you, which makes browsing the API much easier. - -reverse(viewname, request, ...) -------------------------------- - -The :py:func:`~reverse.reverse` function has the same behavior as `django.core.urlresolvers.reverse`_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port:: - - from djangorestframework.reverse import reverse - from djangorestframework.views import View - - class MyView(View): - def get(self, request): - context = { - 'url': reverse('year-summary', request, args=[1945]) - } - - return Response(context) - -reverse_lazy(viewname, request, ...) ------------------------------------- - -The :py:func:`~reverse.reverse_lazy` function has the same behavior as `django.core.urlresolvers.reverse_lazy`_, except that it takes a request object and returns a fully qualified URL, using the request to determine the host and port. - -.. _django.core.urlresolvers.reverse: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse -.. _django.core.urlresolvers.reverse_lazy: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy diff --git a/docs/howto/setup.rst b/docs/howto/setup.rst deleted file mode 100644 index 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 %} -

My API

- {% endblock %} - - -Styling -------- - -Django REST framework requires `django.contrib.staticfiles`_ to serve it's css. -If you're using Django 1.2 you'll need to use the seperate -`django-staticfiles`_ package instead. - -You can override the styling by creating a file in your top-level static -directory named ``djangorestframework/css/style.css`` - - -Markdown --------- - -`Python markdown`_ is not required but comes recommended. - -If markdown is installed your :class:`.Resource` descriptions can include -`markdown formatting`_ which will be rendered by the self-documenting API. - -YAML ----- - -YAML support is optional, and requires `PyYAML`_. - - -Login / Logout --------------- - -Django REST framework includes login and logout views that are needed if -you're using the self-documenting API. - -Make sure you include the following in your `urlconf`:: - - from django.conf.urls.defaults import patterns, url - - urlpatterns = patterns('', - ... - url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework')) - ) - -.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ -.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/ -.. _URLObject: http://pypi.python.org/pypi/URLObject/ -.. _Python markdown: http://www.freewisdom.org/projects/python-markdown/ -.. _markdown formatting: http://daringfireball.net/projects/markdown/syntax -.. _PyYAML: http://pypi.python.org/pypi/PyYAML \ No newline at end of file diff --git a/docs/howto/usingcurl.rst b/docs/howto/usingcurl.rst deleted file mode 100644 index eeb8da06..00000000 --- a/docs/howto/usingcurl.rst +++ /dev/null @@ -1,30 +0,0 @@ -Using CURL with django-rest-framework -===================================== - -`curl `_ is a great command line tool for making requests to URLs. - -There are a few things that can be helpful to remember when using CURL with django-rest-framework APIs. - -#. Curl sends an ``Accept: */*`` header by default:: - - curl -X GET http://example.com/my-api/ - -#. Setting the ``Accept:`` header on a curl request can be useful:: - - curl -X GET -H 'Accept: application/json' http://example.com/my-api/ - -#. The text/plain representation is useful for browsing the API:: - - curl -X GET -H 'Accept: text/plain' http://example.com/my-api/ - -#. ``POST`` and ``PUT`` requests can contain form data (ie ``Content-Type: application/x-www-form-urlencoded``):: - - curl -X PUT --data 'foo=bar' http://example.com/my-api/some-resource/ - -#. Or any other content type:: - - curl -X PUT -H 'Content-Type: application/json' --data-binary '{"foo":"bar"}' http://example.com/my-api/some-resource/ - -#. You can use basic authentication to send the username and password:: - - curl -X GET -H 'Accept: application/json' -u : http://example.com/my-api/ diff --git a/docs/howto/usingurllib2.rst b/docs/howto/usingurllib2.rst deleted file mode 100644 index 6320dc20..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://rest.ep.io/model-resource-example') - >>> r.getcode() # Check if the response was ok - 200 - >>> print r.read() # Examin the response itself - [{"url": "http://rest.ep.io/model-resource-example/1/", "baz": "sdf", "foo": true, "bar": 123}] - -Using the 'POST' method ------------------------ - -And here's an example which does a 'POST' to create a new instance. First let's encode -the data we want to POST. We'll use `urllib` for encoding and the `time` module -to send the current time as as a string value for our POST.:: - - >>> import urllib, time - >>> d = urllib.urlencode((('bar', 123), ('baz', time.asctime()))) - -Now use the `Request` class and specify the 'Content-type':: - - >>> req = urllib2.Request('http://rest.ep.io/model-resource-example/', data=d, headers={'Content-Type':'application/x-www-form-urlencoded'}) - >>> resp = urllib2.urlopen(req) - >>> resp.getcode() - 201 - >>> resp.read() - '{"url": "http://rest.ep.io/model-resource-example/4/", "baz": "Fri Dec 30 18:22:52 2011", "foo": false, "bar": 123}' - -That should get you started to write a client for your own api. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..9bd90d8d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,62 @@ +Quickstart +========== + +**TODO** + +Tutorial +======== + +* [1 - Serialization][tut-1] +* [2 - Requests & Responses][tut-2] +* [3 - Class based views][tut-3] +* [4 - Authentication, permissions & throttling][tut-4] +* [5 - Relationships & hyperlinked APIs][tut-5] +* [6 - Resource orientated projects][tut-6] + +API Guide +========= + +* [Requests][request] +* [Responses][response] +* [Views][views] +* [Parsers][parsers] +* [Renderers][renderers] +* [Serializers][serializers] +* [Authentication][authentication] +* [Permissions][permissions] +* [Status codes][status] + +Topics +====== + +* [Returning URLs][urls] +* [CSRF][csrf] +* [Form overloading][formoverloading] + +Other +===== + +* Why REST framework +* Contributing +* Change Log + +[tut-1]: tutorial/1-serialization.md +[tut-2]: tutorial/2-requests-and-responses.md +[tut-3]: tutorial/3-class-based-views.md +[tut-4]: tutorial/4-authentication-permissions-and-throttling.md +[tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md +[tut-6]: tutorial/6-resource-orientated-projects.md + +[request]: request.md +[response]: response.md +[views]: views.md +[parsers]: parsers.md +[renderers]: renderers.md +[serializers]: serializers.md +[authentication]: authentication.md +[permissions]: permissions.md +[status]: status.md + +[urls]: urls.md +[csrf]: csrf.md +[formoverloading]: formoverloading.md diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index a6745fca..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,128 +0,0 @@ -.. meta:: - :description: A lightweight REST framework for Django. - :keywords: django, python, REST, RESTful, API, interface, framework - - -Django REST framework -===================== - -Introduction ------------- - -Django REST framework is a lightweight REST framework for Django, that aims to make it easy to build well-connected, self-describing RESTful Web APIs. - -**Browse example APIs created with Django REST framework:** `The Sandbox `_ - -Features: ---------- - -* Automatically provides an awesome Django admin style `browse-able self-documenting API `_. -* Clean, simple, views for Resources, using Django's new `class based views `_. -* Support for ModelResources with out-of-the-box default implementations and input validation. -* Pluggable :mod:`.parsers`, :mod:`renderers`, :mod:`authentication` and :mod:`permissions` - Easy to customise. -* Content type negotiation using HTTP Accept headers. -* Optional support for forms as input validation. -* Modular architecture - MixIn classes can be used without requiring the :class:`.Resource` or :class:`.ModelResource` classes. - -Resources ---------- - -**Project hosting:** `GitHub `_. - -* The ``djangorestframework`` package is `available on PyPI `_. -* We have an active `discussion group `_. -* Bug reports are handled on the `issue tracker `_. -* There is a `Jenkins CI server `_ which tracks test status and coverage reporting. (Thanks Marko!) - -Any and all questions, thoughts, bug reports and contributions are *hugely appreciated*. - -Requirements ------------- - -* Python (2.5, 2.6, 2.7 supported) -* Django (1.2, 1.3, 1.4 supported) -* `django.contrib.staticfiles`_ (or `django-staticfiles`_ for Django 1.2) -* `URLObject`_ >= 2.0.0 -* `Markdown`_ >= 2.1.0 (Optional) -* `PyYAML`_ >= 3.10 (Optional) - -Installation ------------- - -You can install Django REST framework using ``pip`` or ``easy_install``:: - - pip install djangorestframework - -Or get the latest development version using git:: - - git clone git@github.com:tomchristie/django-rest-framework.git - -Setup ------ - -To add Django REST framework to a Django project: - -* Ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``. -* Add ``djangorestframework`` to your ``INSTALLED_APPS``. -* Add the following to your URLconf. (To include the REST framework Login/Logout views.):: - - urlpatterns = patterns('', - ... - url(r'^restframework', include('djangorestframework.urls', namespace='djangorestframework')) - ) - -For more information on settings take a look at the :ref:`setup` section. - -Getting Started ---------------- - -Using Django REST framework can be as simple as adding a few lines to your urlconf. - -The following example exposes your `MyModel` model through an api. It will provide two views: - - * A view which lists your model instances and simultaniously allows creation of instances - from that view. - - * Another view which lets you view, update or delete your model instances individually. - -``urls.py``:: - - from django.conf.urls.defaults import patterns, url - from djangorestframework.resources import ModelResource - from djangorestframework.views import ListOrCreateModelView, InstanceModelView - from myapp.models import MyModel - - class MyResource(ModelResource): - model = MyModel - - urlpatterns = patterns('', - url(r'^$', ListOrCreateModelView.as_view(resource=MyResource)), - url(r'^(?P[^/]+)/$', InstanceModelView.as_view(resource=MyResource)), - ) - -.. include:: howto.rst - -.. include:: library.rst - - -.. include:: examples.rst - -.. toctree:: - :hidden: - - contents - -.. include:: ../CHANGELOG.rst - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. _django.contrib.staticfiles: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ -.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles/ -.. _URLObject: http://pypi.python.org/pypi/URLObject/ -.. _Markdown: http://pypi.python.org/pypi/Markdown/ -.. _PyYAML: http://pypi.python.org/pypi/PyYAML diff --git a/docs/library.rst b/docs/library.rst deleted file mode 100644 index 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/request.rst b/docs/library/request.rst deleted file mode 100644 index 5e99826a..00000000 --- a/docs/library/request.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`request` -===================== - -.. automodule:: request - :members: diff --git a/docs/library/resource.rst b/docs/library/resource.rst deleted file mode 100644 index 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/utils.rst b/docs/library/utils.rst deleted file mode 100644 index 653f24fd..00000000 --- a/docs/library/utils.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`utils` -============== - -.. automodule:: utils - :members: diff --git a/docs/library/views.rst b/docs/library/views.rst deleted file mode 100644 index 329b487b..00000000 --- a/docs/library/views.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`views` -===================== - -.. automodule:: views - :members: diff --git a/docs/parsers.md b/docs/parsers.md new file mode 100644 index 00000000..44e33105 --- /dev/null +++ b/docs/parsers.md @@ -0,0 +1,5 @@ +Parsers +======= + +.parse(request) +--------------- diff --git a/docs/renderers.md b/docs/renderers.md new file mode 100644 index 00000000..20cdb8ad --- /dev/null +++ b/docs/renderers.md @@ -0,0 +1,6 @@ +Renderers +========= + +.render(response) +----------------- + diff --git a/docs/request.md b/docs/request.md new file mode 100644 index 00000000..b0491897 --- /dev/null +++ b/docs/request.md @@ -0,0 +1,76 @@ +Request +======= + +> If you're doing REST-based web service stuff ... you should ignore request.POST. +> +> — Malcom Tredinnick, [Django developers group][1] + +The `Request` object in `djangorestframework` extends the standard `HttpRequest`, adding support for parsing multiple content types, allowing browser-based `PUT`, `DELETE` and other methods, and adding flexible per-request authentication. + +method +------ + +`request.method` returns the uppercased string representation of the request's HTTP method. + +Browser-based `PUT`, `DELETE` and other requests are supported, and can be made by using a hidden form field named `_method` in a regular `POST` form. + + + +content_type +------------ + +`request.content`, returns a string object representing the mimetype of the HTTP request's body, if one exists. + + + +DATA +---- + +`request.DATA` returns the parsed content of the request body. This is similar to the standard `HttpRequest.POST` attribute except that: + +1. It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests. +2. It supports parsing multiple content types, rather than just form data. For example you can handle incoming json data in the same way that you handle incoming form data. + +FILES +----- + +`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing that is used for `request.DATA`. + +This allows you to support file uploads from multiple content-types. For example you can write a parser that supports `POST`ing the raw content of a file, instead of using form-encoded file uploads. + +user +---- + +`request.user` returns a `django.contrib.auth.models.User` instance. + +auth +---- + +`request.auth` returns any additional authentication context that may not be contained in `request.user`. The exact behavior of `request.auth` depends on what authentication has been set in `request.authentication`. For many types of authentication this will simply be `None`, but it may also be an object representing a permission scope, an expiry time, or any other information that might be contained in a token-based authentication scheme. + +parsers +------- + +`request.parsers` should be set to a list of `Parser` instances that can be used to parse the content of the request body. + +`request.parsers` may no longer be altered once `request.DATA`, `request.FILES` or `request.POST` have been accessed. + +If you're using the `djangorestframework.views.View` class... **[TODO]** + +stream +------ + +`request.stream` returns a stream representing the content of the request body. + +You will not typically need to access `request.stream`, unless you're writing a `Parser` class. + +authentication +-------------- + +`request.authentication` should be set to a list of `Authentication` instances that can be used to authenticate the request. + +`request.authentication` may no longer be altered once `request.user` or `request.auth` have been accessed. + +If you're using the `djangorestframework.views.View` class... **[TODO]** + +[1]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 46a67149..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Documentation requires Django & Sphinx, and their dependencies... - -Django>=1.2.4 -Jinja2==2.5.5 -Pygments==1.4 -Sphinx==1.0.7 -docutils==0.7 -wsgiref==0.1.2 diff --git a/docs/response.md b/docs/response.md new file mode 100644 index 00000000..d77c9a0d --- /dev/null +++ b/docs/response.md @@ -0,0 +1,27 @@ +Responses +========= + +> HTTP has provisions for several mechanisms for "content negotiation" -- the process of selecting the best representation for a given response when there are multiple representations available. -- RFC 2616, Fielding et al. + +> Unlike basic HttpResponse objects, TemplateResponse objects retain the details of the context that was provided by the view to compute the response. The final output of the response is not computed until it is needed, later in the response process. -- Django documentation. + +Django REST framework supports HTTP content negotiation by providing a `Response` class which allows you to return content that can be rendered into multiple content types, depending on the client request. + +The `Response` class subclasses Django's `TemplateResponse`. It works by allowing you to specify a serializer and a number of different renderers. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. + +There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it does provide a better interface for returning Web API responses. + +Response(content, status, headers=None, serializer=None, renderers=None, format=None) +------------------------------------------------------------------------------------- + +serializer +---------- + +renderers +--------- + +view +---- + +ImmediateResponse(...) +---------------------- \ No newline at end of file diff --git a/docs/serializers.md b/docs/serializers.md new file mode 100644 index 00000000..23e37f40 --- /dev/null +++ b/docs/serializers.md @@ -0,0 +1,47 @@ +Serializers +=========== + +> Expanding the usefulness of the serializers is something that we would +like to address. However, it's not a trivial problem, and it +will take some serious design work. Any offers to help out in this +area would be gratefully accepted. + - Russell Keith-Magee, [Django users group][1] + +Serializers provide a way of filtering the content of responses, prior to the response being rendered. + +They also allow us to use complex data such as querysets and model instances for the content of our responses, and convert that data into native python datatypes that can then be easily rendered into `JSON`, `XML` or whatever. + +REST framework includes a default `Serializer` class which gives you a powerful, generic way to control the output of your responses, but you can also write custom serializers for your data, or create other generic serialization strategies to suit the needs of your API. + +BaseSerializer +-------------- + +This is the base class for all serializers. If you want to provide your own custom serialization, override this class. + +.serialize() +------------ + +Serializer +---------- + +This is the default serializer. + +fields +------ + +include +------- + +exclude +------- + +rename +------ + +related_serializer +------------------ + +depth +----- + +[1]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion diff --git a/docs/status.md b/docs/status.md new file mode 100644 index 00000000..ca866cad --- /dev/null +++ b/docs/status.md @@ -0,0 +1,17 @@ +Status Codes +============ + +> 418 I'm a teapot - Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout. + - RFC 2324 + +REST framework provides a ... +These are simply ... + + from djangorestframework import status + + def view(self): + return Response(status=status.HTTP_404_NOT_FOUND) + +For more information see [RFC 2616](1). + +[1]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html \ No newline at end of file diff --git a/docs/templates/layout.html b/docs/templates/layout.html deleted file mode 100644 index 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 %}{% if pagename == 'index' %}Django REST framework{% else %}{{ titleprefix }}{{ title|striptags|e }}{% endif %}{% endblock %} - -{% block extrahead %} -{{ super() }} - -{% endblock %} -{% block footer %} - diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md new file mode 100644 index 00000000..55a9f679 --- /dev/null +++ b/docs/tutorial/1-serialization.md @@ -0,0 +1,236 @@ +# Tutorial 1: Serialization + +## Introduction + +This tutorial will walk you through the building blocks that make up REST framework. It'll take a little while to get through, but it'll give you a comprehensive understanding of how everything fits together. + +## Getting started + +To get started, let's create a new project to work with. + + django-admin.py startproject tutorial + cd tutorial + +Once that's done we can create an app that we'll use to create a simple Web API. + + python manage.py startapp blog + +The simplest way to get up and running will probably be to use an `sqlite3` database for the tutorial. Edit the `tutorial/settings.py` file, and set the default database `"ENGINE"` to `"sqlite3"`, and `"NAME"` to `"tmp.db"`. + + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'tmp.db', + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + } + } + +We'll also need to add our new `blog` app and the `djangorestframework` app to `INSTALLED_APPS`. + + INSTALLED_APPS = ( + ... + 'djangorestframework', + 'blog' + ) + +We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our blog views. + + urlpatterns = patterns('', + url(r'^', include('blog.urls')), + ) + +Okay, we're ready to roll. + +## Creating a model to work with + +For the purposes of this tutorial we're going to start by creating a simple `Comment` model that is used to store comments against a blog post. Go ahead and edit the `blog` app's `models.py` file. + + from django.db import models + + class Comment(models.Model): + email = models.EmailField() + content = models.CharField(max_length=200) + created = models.DateTimeField(auto_now_add=True) + +Don't forget to sync the database for the first time. + + python manage.py syncdb + +## Creating a Serializer class + +We're going to create a simple Web API that we can use to edit these comment objects with. The first thing we need is a way of serializing and deserializing the objects into representations such as `json`. We do this by declaring serializers, that work very similarly to Django's forms. Create a file in the project named `serializers.py` and add the following. + + from blog import models + from djangorestframework import serializers + + + class CommentSerializer(serializers.Serializer): + email = serializers.EmailField() + content = serializers.CharField(max_length=200) + created = serializers.DateTimeField() + + def restore_object(self, attrs, instance=None): + """ + Create or update a new comment instance. + """ + if instance: + instance.email = attrs['email'] + instance.content = attrs['content'] + instance.created = attrs['created'] + return instance + return models.Comment(**attrs) + +The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. + +We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit. + +## Working with Serializers + +Before we go any further we'll familiarise ourselves with using our new Serializer class. Let's drop into the Django shell. + + python manage.py shell + +Okay, once we've got a few imports out of the way, we'd better create a few comments to work with. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework.renderers import JSONRenderer + from djangorestframework.parsers import JSONParser + + c1 = Comment(email='leila@example.com', content='nothing to say') + c2 = Comment(email='tom@example.com', content='foo bar') + c3 = Comment(email='anna@example.com', content='LOLZ!') + c1.save() + c2.save() + c3.save() + +We've now got a few comment instances to play with. Let's take a look at serializing one of those instances. + + serializer = CommentSerializer(instance=c1) + serializer.data + # {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)} + +At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`. + + stream = JSONRenderer().render(serializer.data) + stream + # '{"email": "leila@example.com", "content": "nothing to say", "created": "2012-08-22T16:20:09.822"}' + +Deserialization is similar. First we parse a stream into python native datatypes... + + data = JSONParser().parse(stream) + +...then we restore those native datatypes into to a fully populated object instance. + + serializer = CommentSerializer(data) + serializer.is_valid() + # True + serializer.object + # + +Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer. + +## Writing regular Django views using our Serializers + +Let's see how we can write some API views using our new Serializer class. +We'll start off by creating a subclass of HttpResponse that we can use to render any data we return into `json`. + +Edit the `blog/views.py` file, and add the following. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework.renderers import JSONRenderer + from djangorestframework.parsers import JSONParser + from django.http import HttpResponse + + + class JSONResponse(HttpResponse): + """ + An HttpResponse that renders it's content into JSON. + """ + + def __init__(self, data, **kwargs): + content = JSONRenderer().render(data) + kwargs['content_type'] = 'application/json' + super(JSONResponse, self).__init__(content, **kwargs) + + +The root of our API is going to be a view that supports listing all the existing comments, or creating a new comment. + + def comment_root(request): + """ + List all comments, or create a new comment. + """ + if request.method == 'GET': + comments = Comment.objects.all() + serializer = CommentSerializer(instance=comments) + return JSONResponse(serializer.data) + + elif request.method == 'POST': + data = JSONParser().parse(request) + serializer = CommentSerializer(data) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return JSONResponse(serializer.data, status=201) + else: + return JSONResponse(serializer.error_data, status=400) + +We'll also need a view which corrosponds to an individual comment, and can be used to retrieve, update or delete the comment. + + def comment_instance(request, pk): + """ + Retrieve, update or delete a comment instance. + """ + try: + comment = Comment.objects.get(pk=pk) + except Comment.DoesNotExist: + return HttpResponse(status=404) + + if request.method == 'GET': + serializer = CommentSerializer(instance=comment) + return JSONResponse(serializer.data) + + elif request.method == 'PUT': + data = JSONParser().parse(request) + serializer = CommentSerializer(data, instance=comment) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return JSONResponse(serializer.data) + else: + return JSONResponse(serializer.error_data, status=400) + + elif request.method == 'DELETE': + comment.delete() + return HttpResponse(status=204) + +Finally we need to wire these views up, in the `tutorial/urls.py` file. + + from django.conf.urls import patterns, url + + urlpatterns = patterns('blog.views', + url(r'^$', 'comment_root'), + url(r'^(?P[0-9]+)$', 'comment_instance') + ) + +It's worth noting that there's a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now. + +## Testing our first attempt at a Web API + +**TODO: Describe using runserver and making example requests from console** + +**TODO: Describe opening in a web browser and viewing json output** + +## Where are we now + +We're doing okay so far, we've got a serialization API that feels pretty similar to Django's Forms API, and some regular Django views. + +Our API views don't do anything particularly special at the moment, beyond serve `json` responses, and there's some error handling edge cases we'd still like to clean up, but it's a functioning Web API. + +We'll see how we can start to improve things in [part 2 of the tutorial][1]. + +[1]: 2-requests-and-responses.md \ No newline at end of file diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md new file mode 100644 index 00000000..2bb6c20e --- /dev/null +++ b/docs/tutorial/2-requests-and-responses.md @@ -0,0 +1,137 @@ +# Tutorial 2: Request and Response objects + +From this point we're going to really start covering the core of REST framework. +Let's introduce a couple of essential building blocks. + +## Request objects + +REST framework intoduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. + + request.POST # Only handles form data. Only works for 'POST' method. + request.DATA # Handles arbitrary data. Works any HTTP request with content. + +## Response objects + +REST framework also introduces a `Response` object, which is a type of `TemplateResponse` that takes unrendered content and uses content negotiation to determine the correct content type to return to the client. + + return Response(data) # Renders to content type as requested by the client. + +## Status codes + +Using numeric HTTP status codes in your views doesn't always make for obvious reading, and it's easy to not notice if you get an error code wrong. REST framework provides more explicit identifiers for each status code, such as `HTTP_400_BAD_REQUEST` in the `status` module. It's a good idea to use these throughout rather than using numeric identifiers. + +## Wrapping API views + +REST framework provides two wrappers you can use to write API views. + +1. The `@api_view` decorator for working with function based views. +2. The `APIView` class for working with class based views. + +These wrappers provide a few bits of functionality such as making sure you recieve `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed. + +The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.DATA` with malformed input. + + +## Pulling it all together + +Okay, let's go ahead and start using these new components to write a few views. + + from djangorestframework.decorators import api_view + from djangorestframework.status import * + + @api_view(allow=['GET', 'POST']) + def comment_root(request): + """ + List all comments, or create a new comment. + """ + if request.method == 'GET': + comments = Comment.objects.all() + serializer = CommentSerializer(instance=comments) + return Response(serializer.data) + + elif request.method == 'POST': + serializer = CommentSerializer(request.DATA) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return Response(serializer.data, status=HTTP_201_CREATED) + else: + return Response(serializer.error_data, status=HTTP_400_BAD_REQUEST) + + +Our instance view is an improvement over the previous example. It's slightly more concise, and the code now feels very similar to if we were working with the Forms API. + + @api_view(allow=['GET', 'PUT', 'DELETE']) + def comment_instance(request, pk): + """ + Retrieve, update or delete a comment instance. + """ + try: + comment = Comment.objects.get(pk=pk) + except Comment.DoesNotExist: + return Response(status=HTTP_404_NOT_FOUND) + + if request.method == 'GET': + serializer = CommentSerializer(instance=comment) + return Response(serializer.data) + + elif request.method == 'PUT': + serializer = CommentSerializer(request.DATA, instance=comment) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return Response(serializer.data) + else: + return Response(serializer.error_data, status=HTTP_400_BAD_REQUEST) + + elif request.method == 'DELETE': + comment.delete() + return Response(status=HTTP_204_NO_CONTENT) + +This should all feel very familiar - it looks a lot like working with forms in regular Django views. + +Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.DATA` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. + +## Adding optional format suffixes to our URLs + +To take advantage of that, let's add support for format suffixes to our API endpoints, so that we can use URLs that explicitly refer to a given format. That means our API will be able to handle URLs such as [http://example.com/api/items/4.json][1]. + +Start by adding a `format` keyword argument to both of the views, like so. + + def comment_root(request, format=None): + +and + + def comment_instance(request, pk, format=None): + +Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs. + + from djangorestframework.urlpatterns import format_suffix_patterns + + urlpatterns = patterns('blogpost.views', + url(r'^$', 'comment_root'), + url(r'^(?P[0-9]+)$', 'comment_instance') + ) + + urlpatterns = format_suffix_patterns(urlpatterns) + +We don't necessarily need to add these extra url patterns in, but it gives us a simple, clean way of refering to a specific format. + +## How's it looking? + +Go ahead and test the API from the command line, as we did in [tutorial part 1][2]. Everything is working pretty similarly, although we've got some nicer error handling if we send invalid requests. + +**TODO: Describe using accept headers, content-type headers, and format suffixed URLs** + +Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/][3]. + +**TODO: Describe browseable API awesomeness** + +## What's next? + +In [tutorial part 3][4], we'll start using class based views, and see how generic views reduce the amount of code we need to write. + +[1]: http://example.com/api/items/4.json +[2]: 1-serialization.md +[3]: http://127.0.0.1:8000/ +[4]: 3-class-based-views.md \ No newline at end of file diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md new file mode 100644 index 00000000..e56c7847 --- /dev/null +++ b/docs/tutorial/3-class-based-views.md @@ -0,0 +1,137 @@ +# Tutorial 3: Using Class Based Views + +We can also write our API views using class based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][1]. + +## Rewriting our API using class based views + +We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring. + + from blog.models import Comment + from blog.serializers import ComentSerializer + from django.http import Http404 + from djangorestframework.views import APIView + from djangorestframework.response import Response + from djangorestframework.status import * + + class CommentRoot(views.APIView): + """ + List all comments, or create a new comment. + """ + def get(self, request, format=None): + comments = Comment.objects.all() + serializer = ComentSerializer(instance=comments) + return Response(serializer.data) + + def post(self, request, format=None) + serializer = ComentSerializer(request.DATA) + if serializer.is_valid(): + comment = serializer.object + comment.save() + return Response(serializer.serialized, status=HTTP_201_CREATED) + else: + return Response(serializer.serialized_errors, status=HTTP_400_BAD_REQUEST) + +So far, so good. It looks pretty similar to the previous case, but we've got better seperation between the different HTTP methods. We'll also need to update the instance view. + + class CommentInstance(views.APIView): + """ + Retrieve, update or delete a comment instance. + """ + + def get_object(self, pk): + try: + return Poll.objects.get(pk=pk) + except Poll.DoesNotExist: + raise Http404 + + def get(self, request, pk, format=None): + comment = self.get_object(pk) + serializer = CommentSerializer(instance=comment) + return Response(serializer.data) + + def put(self, request, pk, format=None): + comment = self.get_object(pk) + serializer = CommentSerializer(request.DATA, instance=comment) + if serializer.is_valid(): + comment = serializer.deserialized + comment.save() + return Response(serializer.data) + else: + return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) + + def delete(self, request, pk, format=None): + comment = self.get_object(pk) + comment.delete() + return Response(status=HTTP_204_NO_CONTENT) + +That's looking good. Again, it's still pretty similar to the function based view right now. + +Since we're now working with class based views, rather than function based views, we'll also need to update our urlconf slightly. + + from blogpost import views + from djangorestframework.urlpatterns import format_suffix_patterns + + urlpatterns = patterns('', + url(r'^$', views.CommentRoot.as_view()), + url(r'^(?P[0-9]+)$', views.CommentInstance.as_view()) + ) + + urlpatterns = format_suffix_patterns(urlpatterns) + +Okay, we're done. If you run the development server everything should be working just as before. + +## Using mixins + +One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour. + +The create/retrieve/update/delete operations that we've been using so far is going to be pretty simliar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes. + +We can compose those mixin classes, to recreate our existing API behaviour with less code. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework import mixins, views + + class CommentRoot(mixins.ListModelQuerysetMixin, + mixins.CreateModelInstanceMixin, + views.BaseRootAPIView): + model = Comment + serializer_class = CommentSerializer + + get = list + post = create + + class CommentInstance(mixins.RetrieveModelInstanceMixin, + mixins.UpdateModelInstanceMixin, + mixins.DestroyModelInstanceMixin, + views.BaseInstanceAPIView): + model = Comment + serializer_class = CommentSerializer + + get = retrieve + put = update + delete = destroy + +## Reusing generic class based views + +That's a lot less code than before, but we can go one step further still. REST framework also provides a set of already mixed-in views. + + from blog.models import Comment + from blog.serializers import CommentSerializer + from djangorestframework import views + + class CommentRoot(views.RootAPIView): + model = Comment + serializer_class = CommentSerializer + + class CommentInstance(views.InstanceAPIView): + model = Comment + serializer_class = CommentSerializer + +Wow, that's pretty concise. We've got a huge amount for free, and our code looks like +good, clean, idomatic Django. + +Next we'll move onto [part 4 of the tutorial][2], where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects. + +[1]: http://en.wikipedia.org/wiki/Don't_repeat_yourself +[2]: 4-authentication-permissions-and-throttling.md diff --git a/docs/tutorial/4-authentication-permissions-and-throttling.md b/docs/tutorial/4-authentication-permissions-and-throttling.md new file mode 100644 index 00000000..5c37ae13 --- /dev/null +++ b/docs/tutorial/4-authentication-permissions-and-throttling.md @@ -0,0 +1,3 @@ +[part 5][5] + +[5]: 5-relationships-and-hyperlinked-apis.md \ No newline at end of file diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md new file mode 100644 index 00000000..3d9598d7 --- /dev/null +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -0,0 +1,9 @@ +**TODO** + +* Create BlogPost model +* Demonstrate nested relationships +* Demonstrate and describe hyperlinked relationships + +[part 6][1] + +[1]: 6-resource-orientated-projects.md diff --git a/docs/tutorial/6-resource-orientated-projects.md b/docs/tutorial/6-resource-orientated-projects.md new file mode 100644 index 00000000..ce51cce5 --- /dev/null +++ b/docs/tutorial/6-resource-orientated-projects.md @@ -0,0 +1,49 @@ +serializers.py + + class BlogPostSerializer(URLModelSerializer): + class Meta: + model = BlogPost + + class CommentSerializer(URLModelSerializer): + class Meta: + model = Comment + +resources.py + + class BlogPostResource(ModelResource): + serializer_class = BlogPostSerializer + model = BlogPost + permissions = [AdminOrAnonReadonly()] + throttles = [AnonThrottle(rate='5/min')] + + class CommentResource(ModelResource): + serializer_class = CommentSerializer + model = Comment + permissions = [AdminOrAnonReadonly()] + throttles = [AnonThrottle(rate='5/min')] + +Now that we're using Resources rather than Views, we don't need to design the urlconf ourselves. The conventions for wiring up resources into views and urls are handled automatically. All we need to do is register the appropriate resources with a router, and let it do the rest. Here's our re-wired `urls.py` file. + + from blog import resources + from djangorestframework.routers import DefaultRouter + + router = DefaultRouter() + router.register(resources.BlogPostResource) + router.register(resources.CommentResource) + urlpatterns = router.urlpatterns + +## Trade-offs between views vs resources. + +Writing resource-orientated code can be a good thing. It helps ensure that URL conventions will be consistent across your APIs, and minimises the amount of code you need to write. + +The trade-off is that the behaviour is less explict. It can be more difficult to determine what code path is being followed, or where to override some behaviour. + +## Onwards and upwards. + +We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start: + +* Contribute on GitHub by reviewing issues, and submitting issues or pull requests. +* Join the REST framework group, and help build the community. +* Follow me on Twitter and say hi. + +Now go build something great. \ No newline at end of file diff --git a/docs/urls.md b/docs/urls.md new file mode 100644 index 00000000..1828dd68 --- /dev/null +++ b/docs/urls.md @@ -0,0 +1,42 @@ +Returning URIs from your Web APIs +================================= + +> The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components. +> -- Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures. + +As a rule, it's probably better practice to return absolute URIs from you web APIs, eg. "http://example.com/foobar", rather than returning relative URIs, eg. "/foobar". + +The advantages of doing so are: + +* It's more explicit. +* It leaves less work for your API clients. +* There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type. +* It allows use to easily do things like markup HTML representations with hyperlinks. + +Django REST framework provides two utility functions to make it more simple to return absolute URIs from your Web API. + +There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink it's output for you, which makes browsing the API much easier. + +reverse(viewname, request, ...) +------------------------------- + +Has the same behavior as [`django.core.urlresolvers.reverse`](1), except that it returns a fully qualified URL, using the request to determine the host and port. + + from djangorestframework.utils import reverse + from djangorestframework.views import View + + class MyView(View): + def get(self, request): + context = { + ... + 'url': reverse('year-summary', request, args=[1945]) + } + return Response(context) + +reverse_lazy(viewname, request, ...) +------------------------------------ + +Has the same behavior as [`django.core.urlresolvers.reverse_lazy`](2), except that it returns a fully qualified URL, using the request to determine the host and port. + +[1]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse +[1]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy \ No newline at end of file diff --git a/docs/views.md b/docs/views.md new file mode 100644 index 00000000..d227339e --- /dev/null +++ b/docs/views.md @@ -0,0 +1,43 @@ +Views +===== + +REST framework provides a simple `View` class, built on Django's `django.generics.views.View`. The `View` class ensures five main things: + +1. Any requests inside the view will become `Request` instances. +2. `Request` instances will have their `renderers` and `authentication` attributes automatically set. +3. `Response` instances will have their `parsers` and `serializer` attributes automatically set. +4. `ImmediateResponse` exceptions will be caught and returned as regular responses. +5. Any permissions provided will be checked prior to passing the request to a handler method. + +Additionally there are a some minor extras, such as providing a default `options` handler, setting some common headers on the response prior to return, and providing the useful `initial()` and `final()` hooks. + +View +---- + +.get(), .post(), .put(), .delete() etc... +----------------------------------------- + +.initial(request, *args, **kwargs) +---------------------------------- + +.final(request, response, *args, **kwargs) +------------------------------------------ + +.parsers +-------- + +.renderers +---------- + +.serializer +----------- + +.authentication +--------------- + +.permissions +------------ + +.headers +-------- + -- cgit v1.2.3 From deedf6957d14c2808c00a009ac2c1d4528cb80c9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 1 Sep 2012 20:26:27 +0100 Subject: REST framework 2 docs --- docs/api-guide/contentnegotiation.md | 1 + docs/api-guide/exceptions.md | 3 + docs/api-guide/parsers.md | 3 + docs/api-guide/renderers.md | 4 + docs/api-guide/requests.md | 66 ++++++++++ docs/api-guide/responses.md | 23 ++++ docs/api-guide/serializers.md | 241 +++++++++++++++++++++++++++++++++++ docs/api-guide/status-codes.md | 93 ++++++++++++++ docs/api-guide/urls.md | 41 ++++++ docs/api-guide/views.md | 39 ++++++ docs/csrf.md | 4 - docs/formoverloading.md | 46 ------- docs/index.md | 132 +++++++++++++++---- docs/mkdocs.py | 56 ++++++++ docs/parsers.md | 5 - docs/renderers.md | 6 - docs/request.md | 76 ----------- docs/response.md | 27 ---- docs/serializers.md | 47 ------- docs/status.md | 17 --- docs/template.html | 150 ++++++++++++++++++++++ docs/topics/csrf.md | 12 ++ docs/topics/formoverloading.md | 43 +++++++ docs/urls.md | 42 ------ docs/views.md | 43 ------- 25 files changed, 880 insertions(+), 340 deletions(-) create mode 100644 docs/api-guide/contentnegotiation.md create mode 100644 docs/api-guide/exceptions.md create mode 100644 docs/api-guide/parsers.md create mode 100644 docs/api-guide/renderers.md create mode 100644 docs/api-guide/requests.md create mode 100644 docs/api-guide/responses.md create mode 100644 docs/api-guide/serializers.md create mode 100644 docs/api-guide/status-codes.md create mode 100644 docs/api-guide/urls.md create mode 100644 docs/api-guide/views.md delete mode 100644 docs/csrf.md delete mode 100644 docs/formoverloading.md create mode 100755 docs/mkdocs.py delete mode 100644 docs/parsers.md delete mode 100644 docs/renderers.md delete mode 100644 docs/request.md delete mode 100644 docs/response.md delete mode 100644 docs/serializers.md delete mode 100644 docs/status.md create mode 100644 docs/template.html create mode 100644 docs/topics/csrf.md create mode 100644 docs/topics/formoverloading.md delete mode 100644 docs/urls.md delete mode 100644 docs/views.md (limited to 'docs') diff --git a/docs/api-guide/contentnegotiation.md b/docs/api-guide/contentnegotiation.md new file mode 100644 index 00000000..f01627d8 --- /dev/null +++ b/docs/api-guide/contentnegotiation.md @@ -0,0 +1 @@ +> HTTP has provisions for several mechanisms for "content negotiation" -- the process of selecting the best representation for a given response when there are multiple representations available. -- RFC 2616, Fielding et al. diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md new file mode 100644 index 00000000..d41327c6 --- /dev/null +++ b/docs/api-guide/exceptions.md @@ -0,0 +1,3 @@ +# Exceptions + + diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md new file mode 100644 index 00000000..2edc11de --- /dev/null +++ b/docs/api-guide/parsers.md @@ -0,0 +1,3 @@ +# Parsers + +## .parse(request) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md new file mode 100644 index 00000000..5a66da69 --- /dev/null +++ b/docs/api-guide/renderers.md @@ -0,0 +1,4 @@ +# Renderers + +## .render(response) + diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md new file mode 100644 index 00000000..67ddfdac --- /dev/null +++ b/docs/api-guide/requests.md @@ -0,0 +1,66 @@ +# Requests + +> If you're doing REST-based web service stuff ... you should ignore request.POST. +> +> — Malcom Tredinnick, [Django developers group][cite] + +REST framework's `Request` class extends the standard `HttpRequest`, adding support for parsing multiple content types, allowing browser-based `PUT`, `DELETE` and other methods, and adding flexible per-request authentication. + +## .method + +`request.method` returns the uppercased string representation of the request's HTTP method. + +Browser-based `PUT`, `DELETE` and other requests are supported, and can be made by using a hidden form field named `_method` in a regular `POST` form. + + + +## .content_type + +`request.content`, returns a string object representing the mimetype of the HTTP request's body, if one exists. + + + +## .DATA + +`request.DATA` returns the parsed content of the request body. This is similar to the standard `HttpRequest.POST` attribute except that: + +1. It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests. +2. It supports parsing multiple content types, rather than just form data. For example you can handle incoming json data in the same way that you handle incoming form data. + +## .FILES + +`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing that is used for `request.DATA`. + +This allows you to support file uploads from multiple content-types. For example you can write a parser that supports `POST`ing the raw content of a file, instead of using form-encoded file uploads. + +## .user + +`request.user` returns a `django.contrib.auth.models.User` instance. + +## .auth + +`request.auth` returns any additional authentication context that may not be contained in `request.user`. The exact behavior of `request.auth` depends on what authentication has been set in `request.authentication`. For many types of authentication this will simply be `None`, but it may also be an object representing a permission scope, an expiry time, or any other information that might be contained in a token-based authentication scheme. + +## .parsers + +`request.parsers` should be set to a list of `Parser` instances that can be used to parse the content of the request body. + +`request.parsers` may no longer be altered once `request.DATA`, `request.FILES` or `request.POST` have been accessed. + +If you're using the `djangorestframework.views.View` class... **[TODO]** + +## .stream + +`request.stream` returns a stream representing the content of the request body. + +You will not typically need to access `request.stream`, unless you're writing a `Parser` class. + +## .authentication + +`request.authentication` should be set to a list of `Authentication` instances that can be used to authenticate the request. + +`request.authentication` may no longer be altered once `request.user` or `request.auth` have been accessed. + +If you're using the `djangorestframework.views.View` class... **[TODO]** + +[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion \ No newline at end of file diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md new file mode 100644 index 00000000..38f6e8cb --- /dev/null +++ b/docs/api-guide/responses.md @@ -0,0 +1,23 @@ +# 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. +> +> — [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 `TemplateResponse`. `Response` objects are initialised with content, 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 does provide a better interface for returning Web API responses. + +## Response(content, headers=None, renderers=None, view=None, format=None, status=None) + + +## .renderers + +## .view + +## .format + + +[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/ \ No newline at end of file diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md new file mode 100644 index 00000000..377b0c10 --- /dev/null +++ b/docs/api-guide/serializers.md @@ -0,0 +1,241 @@ +# Serializers + +> Expanding the usefulness of the serializers is something that we would +like to address. However, it's not a trivial problem, and it +will take some serious design work. Any offers to help out in this +area would be gratefully accepted. +> +> — Russell Keith-Magee, [Django users group][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 + # + >>> 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. + +**TODO: Describe validation in more depth** + +## 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.EmailField() + username = serializers.CharField() + + def restore_object(self, attrs, instance=None): + return User(**attrs) + + + class CommentSerializer(serializers.Serializer): + user = serializers.UserSerializer() + title = serializers.CharField() + content = serializers.CharField(max_length=200) + created = serializers.DateTimeField() + + def restore_object(self, attrs, instance=None): + return Comment(**attrs) + +## 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(Field): + """ + 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(Field): + 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(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(ModelSerializer): + url = CharField(source='get_absolute_url', readonly=True) + group = NaturalKeyField() + + class Meta: + model = Account + +Extra fields can corrospond 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 `PrimaryKeyField` and `NaturalKeyField` 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 represenation 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(ModelSerializer): + class Meta: + model = Account + exclude = ('id',) + +The `fields` and `exclude` options may also be set by passing them to the `serialize()` method. + +**[TODO: Possibly only allow .serialize(fields=…) in FixtureSerializer for backwards compatability, but remove for ModelSerializer]** + +## Specifiying nested serialization + +The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `nested` option: + + class AccountSerializer(ModelSerializer): + class Meta: + model = Account + exclude = ('id',) + nested = True + +The `nested` option may be set to either `True`, `False`, or an integer value. If given an integer value it indicates the depth of relationships that should be traversed before reverting to a flat representation. + +When serializing objects using a nested representation any occurances of recursion will be recognised, and will fall back to using a flat representation. + +The `nested` option may also be set by passing it to the `serialize()` method. + +**[TODO: Possibly only allow .serialize(nested=…) in FixtureSerializer]** + +## Customising the default fields used by a ModelSerializer + + class AccountSerializer(ModelSerializer): + class Meta: + model = Account + + def get_nested_field(self, model_field): + return ModelSerializer() + + def get_related_field(self, model_field): + return NaturalKeyField() + + def get_field(self, model_field): + return Field() + + +[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md new file mode 100644 index 00000000..c1d45905 --- /dev/null +++ b/docs/api-guide/status-codes.md @@ -0,0 +1,93 @@ +# Status Codes + +> 418 I'm a teapot - Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout. +> +> — [RFC 2324][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 djangorestframework 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/urls.md b/docs/api-guide/urls.md new file mode 100644 index 00000000..c39ff8f6 --- /dev/null +++ b/docs/api-guide/urls.md @@ -0,0 +1,41 @@ +# Returning URIs from your Web APIs + +> The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components. +> +> — Roy Fielding, [Architectural Styles and the Design of Network-based Software Architectures][cite] + +As a rule, it's probably better practice to return absolute URIs from you 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 allows use to easily 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(viewname, request, *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. + + from djangorestframework.utils import reverse + from djangorestframework.views import APIView + + class MyView(APIView): + def get(self, request): + content = { + ... + 'url': reverse('year-summary', request, args=[1945]) + } + return Response(content) + +## reverse_lazy(viewname, request, *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. + +[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 \ No newline at end of file diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md new file mode 100644 index 00000000..dd1dbebe --- /dev/null +++ b/docs/api-guide/views.md @@ -0,0 +1,39 @@ +> Django's class based views are a welcome departure from the old-style views. +> +> — [Reinout van Rees][cite] + +# Views + +REST framework provides a simple `APIView` class, built on Django's `django.generics.views.View`. The `APIView` class ensures five main things: + +1. Any requests inside the view will become `Request` instances. +2. `Request` instances will have their `renderers` and `authentication` attributes automatically set. +3. `Response` instances will have their `parsers` and `serializer` attributes automatically set. +4. `APIException` exceptions will be caught and return appropriate responses. +5. Any permissions provided will be checked prior to passing the request to a handler method. + +Additionally there are a some minor extras, such as providing a default `options` handler, setting some common headers on the response prior to return, and providing the useful `initial()` and `final()` hooks. + +## APIView + +## Method handlers + +Describe that APIView handles regular .get(), .post(), .put(), .delete() etc... + +## .initial(request, *args, **kwargs) + +## .final(request, response, *args, **kwargs) + +## .parsers + +## .renderers + +## .serializer + +## .authentication + +## .permissions + +## .headers + +[cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html \ No newline at end of file diff --git a/docs/csrf.md b/docs/csrf.md deleted file mode 100644 index 8e0b9480..00000000 --- a/docs/csrf.md +++ /dev/null @@ -1,4 +0,0 @@ -REST framework and CSRF protection -================================== - -> "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability -- very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one." - Jeff Atwood \ No newline at end of file diff --git a/docs/formoverloading.md b/docs/formoverloading.md deleted file mode 100644 index cab47db9..00000000 --- a/docs/formoverloading.md +++ /dev/null @@ -1,46 +0,0 @@ -Supporting browser-based PUT & DELETE -===================================== - -> "There are two noncontroversial uses for overloaded POST. The first is to *simulate* HTTP's uniform interface for clients like web browsers that don't support PUT or DELETE" - [RESTful Web Services](1), Leonard Richardson & Sam Ruby. - -This is the same strategy as is used in [Ruby on Rails](2). - -Overloading the HTTP method ---------------------------- - -For example, given the following form: - -
- -
- -`request.method` would return `"DELETE"`. - -Overloading the HTTP content type ---------------------------------- - -Browser-based submission of content types other than form are supported by using form fields named `_content` and `_content_type`: - -For example, given the following form: - -
- - -
- -`request.content_type` would return `"application/json"`, and `request.content` would return `"{'count': 1}"` - -Why not just use Javascript? -============================ - -**[TODO]** - -Doesn't HTML5 support PUT and DELETE forms? -=========================================== - -Nope. It was at one point intended to support `PUT` and `DELETE` forms, but was later [dropped from the spec](3). There remains [ongoing discussion](4) about adding support for `PUT` and `DELETE`, as well as how to support content-types other than form-encoded data. - -[1]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260 -[2]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work -[3]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24 -[4]: http://amundsen.com/examples/put-delete-forms/ diff --git a/docs/index.md b/docs/index.md index 9bd90d8d..f309c939 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,62 @@ -Quickstart -========== +# Django REST framework + +**A toolkit for building well-connected, self-describing Web APIs.** + +**WARNING: This documentation is for the 2.0 redesign of REST framework. It is a work in progress.** + +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. + +## Requirements + +REST framework requires the following: + +* Python (2.6, 2.7) +* Django (1.3, 1.4, 1.5) +* [URLObject][urlobject] (2.0.0+) + +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. + +If you're installing using `pip`, all requirements and optional packages will be installed by default. + +## Installation + +**WARNING: These instructions will only become valid once this becomes the master version** + +Install using `pip`... + + pip install djangorestframework + +...or clone the project from github. + + git clone git@github.com:tomchristie/django-rest-framework.git + pip install -r requirements.txt + +Add `djangorestframework` to your `INSTALLED_APPS`. + + INSTALLED_APPS = ( + ... + 'djangorestframework', + ) + +If you're intending to use the browserable 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'^auth', include('djangorestframework.urls', namespace='djangorestframework')) + ) + +## Quickstart **TODO** -Tutorial -======== +## 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] @@ -13,8 +65,9 @@ Tutorial * [5 - Relationships & hyperlinked APIs][tut-5] * [6 - Resource orientated projects][tut-6] -API Guide -========= +## API Guide + +The API guide is your complete reference manual to all the functionality provided by REST framework. * [Requests][request] * [Responses][response] @@ -24,21 +77,45 @@ API Guide * [Serializers][serializers] * [Authentication][authentication] * [Permissions][permissions] +* [Exceptions][exceptions] * [Status codes][status] +* [Returning URLs][urls] -Topics -====== +## Topics + +General guides to using REST framework. -* [Returning URLs][urls] * [CSRF][csrf] * [Form overloading][formoverloading] -Other -===== +## 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: -* Why REST framework -* Contributing -* Change Log +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. + +[urlobject]: https://github.com/zacharyvoase/urlobject +[markdown]: http://pypi.python.org/pypi/Markdown/ +[yaml]: http://pypi.python.org/pypi/PyYAML [tut-1]: tutorial/1-serialization.md [tut-2]: tutorial/2-requests-and-responses.md @@ -47,16 +124,17 @@ Other [tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md [tut-6]: tutorial/6-resource-orientated-projects.md -[request]: request.md -[response]: response.md -[views]: views.md -[parsers]: parsers.md -[renderers]: renderers.md -[serializers]: serializers.md -[authentication]: authentication.md -[permissions]: permissions.md -[status]: status.md - -[urls]: urls.md -[csrf]: csrf.md -[formoverloading]: formoverloading.md +[request]: api-guide/requests.md +[response]: api-guide/responses.md +[views]: api-guide/views.md +[parsers]: api-guide/parsers.md +[renderers]: api-guide/renderers.md +[serializers]: api-guide/serializers.md +[authentication]: api-guide/authentication.md +[permissions]: api-guide/permissions.md +[exceptions]: api-guide/exceptions.md +[status]: api-guide/status.md +[urls]: api-guide/urls.md + +[csrf]: topics/csrf.md +[formoverloading]: topics/formoverloading.md diff --git a/docs/mkdocs.py b/docs/mkdocs.py new file mode 100755 index 00000000..f984e6f9 --- /dev/null +++ b/docs/mkdocs.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +import markdown +import os +import re + +root = os.path.dirname(__file__) +local = True + +if local: + base_url = 'file://%s/html/' % os.path.normpath(os.path.join(os.getcwd(), root)) + suffix = '.html' + index = 'index.html' +else: + base_url = 'http://tomchristie.github.com/restframeworkdocs/' + suffix = '' + index = '' + + +main_header = '
  • {{ title }}
  • ' +sub_header = '
  • {{ title }}
  • ' + +page = open(os.path.join(root, 'template.html'), 'r').read() + +for (dirpath, dirnames, filenames) in os.walk(root): + for filename in filenames: + if not filename.endswith('.md'): + continue + + toc = '' + text = open(os.path.join(dirpath, filename), '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']) + + build_dir = os.path.join(root, 'html', dirpath) + build_file = os.path.join(build_dir, filename[:-3] + '.html') + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + output = page.replace('{{ content }}', content).replace('{{ toc }}', toc).replace('{{ base_url }}', base_url).replace('{{ suffix }}', suffix).replace('{{ index }}', index) + output = re.sub(r'a href="([^"]*)\.md"', r'a href="\1.html"', output) + open(build_file, 'w').write(output.encode('utf-8')) diff --git a/docs/parsers.md b/docs/parsers.md deleted file mode 100644 index 44e33105..00000000 --- a/docs/parsers.md +++ /dev/null @@ -1,5 +0,0 @@ -Parsers -======= - -.parse(request) ---------------- diff --git a/docs/renderers.md b/docs/renderers.md deleted file mode 100644 index 20cdb8ad..00000000 --- a/docs/renderers.md +++ /dev/null @@ -1,6 +0,0 @@ -Renderers -========= - -.render(response) ------------------ - diff --git a/docs/request.md b/docs/request.md deleted file mode 100644 index b0491897..00000000 --- a/docs/request.md +++ /dev/null @@ -1,76 +0,0 @@ -Request -======= - -> If you're doing REST-based web service stuff ... you should ignore request.POST. -> -> — Malcom Tredinnick, [Django developers group][1] - -The `Request` object in `djangorestframework` extends the standard `HttpRequest`, adding support for parsing multiple content types, allowing browser-based `PUT`, `DELETE` and other methods, and adding flexible per-request authentication. - -method ------- - -`request.method` returns the uppercased string representation of the request's HTTP method. - -Browser-based `PUT`, `DELETE` and other requests are supported, and can be made by using a hidden form field named `_method` in a regular `POST` form. - - - -content_type ------------- - -`request.content`, returns a string object representing the mimetype of the HTTP request's body, if one exists. - - - -DATA ----- - -`request.DATA` returns the parsed content of the request body. This is similar to the standard `HttpRequest.POST` attribute except that: - -1. It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests. -2. It supports parsing multiple content types, rather than just form data. For example you can handle incoming json data in the same way that you handle incoming form data. - -FILES ------ - -`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing that is used for `request.DATA`. - -This allows you to support file uploads from multiple content-types. For example you can write a parser that supports `POST`ing the raw content of a file, instead of using form-encoded file uploads. - -user ----- - -`request.user` returns a `django.contrib.auth.models.User` instance. - -auth ----- - -`request.auth` returns any additional authentication context that may not be contained in `request.user`. The exact behavior of `request.auth` depends on what authentication has been set in `request.authentication`. For many types of authentication this will simply be `None`, but it may also be an object representing a permission scope, an expiry time, or any other information that might be contained in a token-based authentication scheme. - -parsers -------- - -`request.parsers` should be set to a list of `Parser` instances that can be used to parse the content of the request body. - -`request.parsers` may no longer be altered once `request.DATA`, `request.FILES` or `request.POST` have been accessed. - -If you're using the `djangorestframework.views.View` class... **[TODO]** - -stream ------- - -`request.stream` returns a stream representing the content of the request body. - -You will not typically need to access `request.stream`, unless you're writing a `Parser` class. - -authentication --------------- - -`request.authentication` should be set to a list of `Authentication` instances that can be used to authenticate the request. - -`request.authentication` may no longer be altered once `request.user` or `request.auth` have been accessed. - -If you're using the `djangorestframework.views.View` class... **[TODO]** - -[1]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion \ No newline at end of file diff --git a/docs/response.md b/docs/response.md deleted file mode 100644 index d77c9a0d..00000000 --- a/docs/response.md +++ /dev/null @@ -1,27 +0,0 @@ -Responses -========= - -> HTTP has provisions for several mechanisms for "content negotiation" -- the process of selecting the best representation for a given response when there are multiple representations available. -- RFC 2616, Fielding et al. - -> Unlike basic HttpResponse objects, TemplateResponse objects retain the details of the context that was provided by the view to compute the response. The final output of the response is not computed until it is needed, later in the response process. -- Django documentation. - -Django REST framework supports HTTP content negotiation by providing a `Response` class which allows you to return content that can be rendered into multiple content types, depending on the client request. - -The `Response` class subclasses Django's `TemplateResponse`. It works by allowing you to specify a serializer and a number of different renderers. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. - -There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it does provide a better interface for returning Web API responses. - -Response(content, status, headers=None, serializer=None, renderers=None, format=None) -------------------------------------------------------------------------------------- - -serializer ----------- - -renderers ---------- - -view ----- - -ImmediateResponse(...) ----------------------- \ No newline at end of file diff --git a/docs/serializers.md b/docs/serializers.md deleted file mode 100644 index 23e37f40..00000000 --- a/docs/serializers.md +++ /dev/null @@ -1,47 +0,0 @@ -Serializers -=========== - -> Expanding the usefulness of the serializers is something that we would -like to address. However, it's not a trivial problem, and it -will take some serious design work. Any offers to help out in this -area would be gratefully accepted. - - Russell Keith-Magee, [Django users group][1] - -Serializers provide a way of filtering the content of responses, prior to the response being rendered. - -They also allow us to use complex data such as querysets and model instances for the content of our responses, and convert that data into native python datatypes that can then be easily rendered into `JSON`, `XML` or whatever. - -REST framework includes a default `Serializer` class which gives you a powerful, generic way to control the output of your responses, but you can also write custom serializers for your data, or create other generic serialization strategies to suit the needs of your API. - -BaseSerializer --------------- - -This is the base class for all serializers. If you want to provide your own custom serialization, override this class. - -.serialize() ------------- - -Serializer ----------- - -This is the default serializer. - -fields ------- - -include -------- - -exclude -------- - -rename ------- - -related_serializer ------------------- - -depth ------ - -[1]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion diff --git a/docs/status.md b/docs/status.md deleted file mode 100644 index ca866cad..00000000 --- a/docs/status.md +++ /dev/null @@ -1,17 +0,0 @@ -Status Codes -============ - -> 418 I'm a teapot - Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout. - - RFC 2324 - -REST framework provides a ... -These are simply ... - - from djangorestframework import status - - def view(self): - return Response(status=status.HTTP_404_NOT_FOUND) - -For more information see [RFC 2616](1). - -[1]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html \ No newline at end of file diff --git a/docs/template.html b/docs/template.html new file mode 100644 index 00000000..a8a0d741 --- /dev/null +++ b/docs/template.html @@ -0,0 +1,150 @@ + + + + Django REST framework + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +
    + +
    +{{ content }} +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/docs/topics/csrf.md b/docs/topics/csrf.md new file mode 100644 index 00000000..a2ee1b9c --- /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." +> +> — [Jeff Atwood][cite] + +* Explain need to add CSRF token to AJAX requests. +* Explain defered 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 \ No newline at end of file diff --git a/docs/topics/formoverloading.md b/docs/topics/formoverloading.md new file mode 100644 index 00000000..a1828c3b --- /dev/null +++ b/docs/topics/formoverloading.md @@ -0,0 +1,43 @@ +# Browser based PUT & DELETE + +> "There are two noncontroversial uses for overloaded POST. The first is to *simulate* HTTP's uniform interface for clients like web browsers that don't support PUT or DELETE" +> +> — [RESTful Web Services](1), Leonard Richardson & Sam Ruby. + +## Overloading the HTTP method + +**TODO: Preamble.** Note that this is the same strategy as is used in [Ruby on Rails](2). + +For example, given the following form: + +
    + +
    + +`request.method` would return `"DELETE"`. + +## Overloading the HTTP content type + +Browser-based submission of content types other than form are supported by using form fields named `_content` and `_content_type`: + +For example, given the following form: + +
    + + +
    + +`request.content_type` would return `"application/json"`, and `request.content` would return `"{'count': 1}"` + +## Why not just use Javascript? + +**[TODO]** + +## Doesn't HTML5 support PUT and DELETE forms? + +Nope. It was at one point intended to support `PUT` and `DELETE` forms, but was later [dropped from the spec](3). There remains [ongoing discussion](4) about adding support for `PUT` and `DELETE`, as well as how to support content-types other than form-encoded data. + +[1]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260 +[2]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work +[3]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24 +[4]: http://amundsen.com/examples/put-delete-forms/ diff --git a/docs/urls.md b/docs/urls.md deleted file mode 100644 index 1828dd68..00000000 --- a/docs/urls.md +++ /dev/null @@ -1,42 +0,0 @@ -Returning URIs from your Web APIs -================================= - -> The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components. -> -- Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures. - -As a rule, it's probably better practice to return absolute URIs from you web APIs, eg. "http://example.com/foobar", rather than returning relative URIs, eg. "/foobar". - -The advantages of doing so are: - -* It's more explicit. -* It leaves less work for your API clients. -* There's no ambiguity about the meaning of the string when it's found in representations such as JSON that do not have a native URI type. -* It allows use to easily do things like markup HTML representations with hyperlinks. - -Django REST framework provides two utility functions to make it more simple to return absolute URIs from your Web API. - -There's no requirement for you to use them, but if you do then the self-describing API will be able to automatically hyperlink it's output for you, which makes browsing the API much easier. - -reverse(viewname, request, ...) -------------------------------- - -Has the same behavior as [`django.core.urlresolvers.reverse`](1), except that it returns a fully qualified URL, using the request to determine the host and port. - - from djangorestframework.utils import reverse - from djangorestframework.views import View - - class MyView(View): - def get(self, request): - context = { - ... - 'url': reverse('year-summary', request, args=[1945]) - } - return Response(context) - -reverse_lazy(viewname, request, ...) ------------------------------------- - -Has the same behavior as [`django.core.urlresolvers.reverse_lazy`](2), except that it returns a fully qualified URL, using the request to determine the host and port. - -[1]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse -[1]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy \ No newline at end of file diff --git a/docs/views.md b/docs/views.md deleted file mode 100644 index d227339e..00000000 --- a/docs/views.md +++ /dev/null @@ -1,43 +0,0 @@ -Views -===== - -REST framework provides a simple `View` class, built on Django's `django.generics.views.View`. The `View` class ensures five main things: - -1. Any requests inside the view will become `Request` instances. -2. `Request` instances will have their `renderers` and `authentication` attributes automatically set. -3. `Response` instances will have their `parsers` and `serializer` attributes automatically set. -4. `ImmediateResponse` exceptions will be caught and returned as regular responses. -5. Any permissions provided will be checked prior to passing the request to a handler method. - -Additionally there are a some minor extras, such as providing a default `options` handler, setting some common headers on the response prior to return, and providing the useful `initial()` and `final()` hooks. - -View ----- - -.get(), .post(), .put(), .delete() etc... ------------------------------------------ - -.initial(request, *args, **kwargs) ----------------------------------- - -.final(request, response, *args, **kwargs) ------------------------------------------- - -.parsers --------- - -.renderers ----------- - -.serializer ------------ - -.authentication ---------------- - -.permissions ------------- - -.headers --------- - -- cgit v1.2.3 From 99415564741ca849c0771a3cdd3c18a72b74a373 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 1 Sep 2012 21:23:50 +0100 Subject: Get docs ready to deploy --- docs/index.md | 2 + docs/mkdocs.py | 56 - docs/static/css/bootstrap-responsive.css | 1040 ++++++ docs/static/css/bootstrap.css | 5624 ++++++++++++++++++++++++++++++ docs/static/js/bootstrap-alert.js | 90 + docs/static/js/bootstrap-button.js | 96 + docs/static/js/bootstrap-carousel.js | 176 + docs/static/js/bootstrap-collapse.js | 158 + docs/static/js/bootstrap-dropdown.js | 150 + docs/static/js/bootstrap-modal.js | 239 ++ docs/static/js/bootstrap-popover.js | 103 + docs/static/js/bootstrap-scrollspy.js | 151 + docs/static/js/bootstrap-tab.js | 135 + docs/static/js/bootstrap-tooltip.js | 275 ++ docs/static/js/bootstrap-transition.js | 60 + docs/static/js/bootstrap-typeahead.js | 300 ++ docs/static/js/jquery.js | 4 + docs/topics/credits.md | 53 + 18 files changed, 8656 insertions(+), 56 deletions(-) delete mode 100755 docs/mkdocs.py create mode 100644 docs/static/css/bootstrap-responsive.css create mode 100644 docs/static/css/bootstrap.css create mode 100644 docs/static/js/bootstrap-alert.js create mode 100644 docs/static/js/bootstrap-button.js create mode 100644 docs/static/js/bootstrap-carousel.js create mode 100644 docs/static/js/bootstrap-collapse.js create mode 100644 docs/static/js/bootstrap-dropdown.js create mode 100644 docs/static/js/bootstrap-modal.js create mode 100644 docs/static/js/bootstrap-popover.js create mode 100644 docs/static/js/bootstrap-scrollspy.js create mode 100644 docs/static/js/bootstrap-tab.js create mode 100644 docs/static/js/bootstrap-tooltip.js create mode 100644 docs/static/js/bootstrap-transition.js create mode 100644 docs/static/js/bootstrap-typeahead.js create mode 100644 docs/static/js/jquery.js create mode 100644 docs/topics/credits.md (limited to 'docs') diff --git a/docs/index.md b/docs/index.md index f309c939..340c6734 100644 --- a/docs/index.md +++ b/docs/index.md @@ -87,6 +87,7 @@ General guides to using REST framework. * [CSRF][csrf] * [Form overloading][formoverloading] +* [Credits][credits] ## License @@ -138,3 +139,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [csrf]: topics/csrf.md [formoverloading]: topics/formoverloading.md +[credits]: topics/credits.md diff --git a/docs/mkdocs.py b/docs/mkdocs.py deleted file mode 100755 index f984e6f9..00000000 --- a/docs/mkdocs.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -import markdown -import os -import re - -root = os.path.dirname(__file__) -local = True - -if local: - base_url = 'file://%s/html/' % os.path.normpath(os.path.join(os.getcwd(), root)) - suffix = '.html' - index = 'index.html' -else: - base_url = 'http://tomchristie.github.com/restframeworkdocs/' - suffix = '' - index = '' - - -main_header = '
  • {{ title }}
  • ' -sub_header = '
  • {{ title }}
  • ' - -page = open(os.path.join(root, 'template.html'), 'r').read() - -for (dirpath, dirnames, filenames) in os.walk(root): - for filename in filenames: - if not filename.endswith('.md'): - continue - - toc = '' - text = open(os.path.join(dirpath, filename), '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']) - - build_dir = os.path.join(root, 'html', dirpath) - build_file = os.path.join(build_dir, filename[:-3] + '.html') - - if not os.path.exists(build_dir): - os.makedirs(build_dir) - output = page.replace('{{ content }}', content).replace('{{ toc }}', toc).replace('{{ base_url }}', base_url).replace('{{ suffix }}', suffix).replace('{{ index }}', index) - output = re.sub(r'a href="([^"]*)\.md"', r'a href="\1.html"', output) - open(build_file, 'w').write(output.encode('utf-8')) diff --git a/docs/static/css/bootstrap-responsive.css b/docs/static/css/bootstrap-responsive.css new file mode 100644 index 00000000..daafa918 --- /dev/null +++ b/docs/static/css/bootstrap-responsive.css @@ -0,0 +1,1040 @@ +/*! + * Bootstrap Responsive v2.1.0 + * + * 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; + 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; + 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 { + 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: auto; + margin-left: 0; + } + .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; + } + .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-group > 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: #555555; + text-shadow: none; + } + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: #555555; + -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 .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 .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; + } +} diff --git a/docs/static/css/bootstrap.css b/docs/static/css/bootstrap.css new file mode 100644 index 00000000..0664207a --- /dev/null +++ b/docs/static/css/bootstrap.css @@ -0,0 +1,5624 @@ +/*! + * Bootstrap v2.1.0 + * + * 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 { + 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; + 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: 20px; + 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; +} + +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 dt { + float: left; + width: 120px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dl-horizontal dd { + margin-left: 130px; +} + +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 { + width: 210px; +} + +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 #bbb; +} + +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; + 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 .checkbox:focus, +.control-group.warning .radio:focus, +.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; + 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 .checkbox:focus, +.control-group.error .radio:focus, +.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; + 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 .checkbox:focus, +.control-group.success .radio:focus, +.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; +} + +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 { + margin-left: -1px; + 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: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: 140px; + padding-top: 5px; + text-align: right; +} + +.form-horizontal .controls { + *display: inline-block; + *padding-left: 20px; + margin-left: 160px; + *margin-left: 0; +} + +.form-horizontal .controls:first-child { + *padding-left: 160px; +} + +.form-horizontal .help-block { + margin-top: 10px; + margin-bottom: 0; +} + +.form-horizontal .form-actions { + padding-left: 160px; +} + +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-right-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.info td { + background-color: #d9edf7; +} + +[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 > .active > a > [class^="icon-"], +.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: "\2191"; +} + +.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; + overflow: visible \9; + -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: 16px; +} + +.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; +} + +.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 { + 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-group { + position: relative; + *margin-left: .3em; + font-size: 0; + white-space: nowrap; +} + +.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: #555555; +} + +.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); + -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 .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: #555555; + text-shadow: 0 1px 0 #ffffff; +} + +.navbar .brand:hover { + text-decoration: none; +} + +.navbar-text { + margin-bottom: 0; + line-height: 40px; +} + +.navbar-link { + color: #555555; +} + +.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: 6px; +} + +.navbar .btn-group .btn { + margin: 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-fixed-bottom .navbar-inner, +.navbar-static-top .navbar-inner { + border: 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; +} + +.navbar .nav > li { + float: left; +} + +.navbar .nav > li > a { + float: none; + padding: 10px 15px 10px; + color: #555555; + 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: #555555; + border-bottom-color: #555555; +} + +.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 li { + display: inline; +} + +.pagination a, +.pagination span { + float: left; + padding: 0 14px; + line-height: 38px; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; + border-left-width: 0; +} + +.pagination a:hover, +.pagination .active a, +.pagination .active span { + background-color: #f5f5f5; +} + +.pagination .active a, +.pagination .active span { + color: #999999; + cursor: default; +} + +.pagination .disabled span, +.pagination .disabled a, +.pagination .disabled a:hover { + color: #999999; + cursor: default; + background-color: transparent; +} + +.pagination li:first-child a, +.pagination 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 li:last-child a, +.pagination 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 { + 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 { + float: right; +} + +.pager .previous a { + float: left; +} + +.pager .disabled a, +.pager .disabled a:hover { + color: #999999; + cursor: default; + background-color: #fff; +} + +.modal-open .dropdown-menu { + z-index: 2050; +} + +.modal-open .dropdown.open { + *z-index: 2050; +} + +.modal-open .popover { + z-index: 2060; +} + +.modal-open .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; +} diff --git a/docs/static/js/bootstrap-alert.js b/docs/static/js/bootstrap-alert.js new file mode 100644 index 00000000..4dd31adb --- /dev/null +++ b/docs/static/js/bootstrap-alert.js @@ -0,0 +1,90 @@ +/* ========================================================== + * bootstrap-alert.js v2.1.0 + * http://twitter.github.com/bootstrap/javascript.html#alerts + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* ALERT CLASS DEFINITION + * ====================== */ + + var dismiss = '[data-dismiss="alert"]' + , Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + + e && e.preventDefault() + + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) + + $parent.trigger(e = $.Event('close')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent + .trigger('closed') + .remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent.on($.support.transition.end, removeElement) : + removeElement() + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('alert') + if (!data) $this.data('alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + /* ALERT DATA-API + * ============== */ + + $(function () { + $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/docs/static/js/bootstrap-button.js b/docs/static/js/bootstrap-button.js new file mode 100644 index 00000000..d0413d6e --- /dev/null +++ b/docs/static/js/bootstrap-button.js @@ -0,0 +1,96 @@ +/* ============================================================ + * bootstrap-button.js v2.1.0 + * http://twitter.github.com/bootstrap/javascript.html#buttons + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* BUTTON PUBLIC CLASS DEFINITION + * ============================== */ + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.button.defaults, options) + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + , $el = this.$element + , data = $el.data() + , val = $el.is('input') ? 'val' : 'html' + + state = state + 'Text' + data.resetText || $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d) + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.parent('[data-toggle="buttons-radio"]') + + $parent && $parent + .find('.active') + .removeClass('active') + + this.$element.toggleClass('active') + } + + + /* BUTTON PLUGIN DEFINITION + * ======================== */ + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('button') + , options = typeof option == 'object' && option + if (!data) $this.data('button', (data = new Button(this, options))) + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.defaults = { + loadingText: 'loading...' + } + + $.fn.button.Constructor = Button + + + /* BUTTON DATA-API + * =============== */ + + $(function () { + $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/docs/static/js/bootstrap-carousel.js b/docs/static/js/bootstrap-carousel.js new file mode 100644 index 00000000..0b87eb8a --- /dev/null +++ b/docs/static/js/bootstrap-carousel.js @@ -0,0 +1,176 @@ +/* ========================================================== + * bootstrap-carousel.js v2.1.0 + * http://twitter.github.com/bootstrap/javascript.html#carousel + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CAROUSEL CLASS DEFINITION + * ========================= */ + + var Carousel = function (element, options) { + this.$element = $(element) + this.options = options + this.options.slide && this.slide(this.options.slide) + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.prototype = { + + cycle: function (e) { + if (!e) this.paused = false + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + return this + } + + , to: function (pos) { + var $active = this.$element.find('.item.active') + , children = $active.parent().children() + , activePos = children.index($active) + , that = this + + if (pos > (children.length - 1) || pos < 0) return + + if (this.sliding) { + return this.$element.one('slid', function () { + that.to(pos) + }) + } + + if (activePos == pos) { + return this.pause().cycle() + } + + return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) + } + + , pause: function (e) { + if (!e) this.paused = true + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle() + } + clearInterval(this.interval) + this.interval = null + return this + } + + , next: function () { + if (this.sliding) return + return this.slide('next') + } + + , prev: function () { + if (this.sliding) return + return this.slide('prev') + } + + , slide: function (type, next) { + var $active = this.$element.find('.item.active') + , $next = next || $active[type]() + , isCycling = this.interval + , direction = type == 'next' ? 'left' : 'right' + , fallback = type == 'next' ? 'first' : 'last' + , that = this + , e = $.Event('slide', { + relatedTarget: $next[0] + }) + + this.sliding = true + + isCycling && this.pause() + + $next = $next.length ? $next : this.$element.find('.item')[fallback]() + + if ($next.hasClass('active')) return + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + this.$element.one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + } + + + /* CAROUSEL PLUGIN DEFINITION + * ========================== */ + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('carousel') + , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) + , action = typeof option == 'string' ? option : options.slide + if (!data) $this.data('carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.cycle() + }) + } + + $.fn.carousel.defaults = { + interval: 5000 + , pause: 'hover' + } + + $.fn.carousel.Constructor = Carousel + + + /* CAROUSEL DATA-API + * ================= */ + + $(function () { + $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) + $target.carousel(options) + e.preventDefault() + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/docs/static/js/bootstrap-collapse.js b/docs/static/js/bootstrap-collapse.js new file mode 100644 index 00000000..391d9128 --- /dev/null +++ b/docs/static/js/bootstrap-collapse.js @@ -0,0 +1,158 @@ +/* ============================================================= + * bootstrap-collapse.js v2.1.0 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* COLLAPSE PUBLIC CLASS DEFINITION + * ================================ */ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options.parent) { + this.$parent = $(this.options.parent) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension + , scroll + , actives + , hasData + + if (this.transitioning) return + + dimension = this.dimension() + scroll = $.camelCase(['scroll', dimension].join('-')) + actives = this.$parent && this.$parent.find('> .accordion-group > .in') + + if (actives && actives.length) { + hasData = actives.data('collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', $.Event('show'), 'shown') + $.support.transition && this.$element[dimension](this.$element[0][scroll]) + } + + , hide: function () { + var dimension + if (this.transitioning) return + dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', $.Event('hide'), 'hidden') + this.$element[dimension](0) + } + + , reset: function (size) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function (method, startEvent, completeEvent) { + var that = this + , complete = function () { + if (startEvent.type == 'show') that.reset() + that.transitioning = 0 + that.$element.trigger(completeEvent) + } + + this.$element.trigger(startEvent) + + if (startEvent.isDefaultPrevented()) return + + this.transitioning = 1 + + this.$element[method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + + /* COLLAPSIBLE PLUGIN DEFINITION + * ============================== */ + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = typeof option == 'object' && option + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API + * ==================== */ + + $(function () { + $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + $(target).collapse(option) + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/docs/static/js/bootstrap-dropdown.js b/docs/static/js/bootstrap-dropdown.js new file mode 100644 index 00000000..ab601e9e --- /dev/null +++ b/docs/static/js/bootstrap-dropdown.js @@ -0,0 +1,150 @@ +/* ============================================================ + * bootstrap-dropdown.js v2.1.0 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle=dropdown]' + , Dropdown = function (element) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function (e) { + var $this = $(this) + , $parent + , isActive + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + $parent.toggleClass('open') + $this.focus() + } + + return false + } + + , keydown: function (e) { + var $this + , $items + , $active + , $parent + , isActive + , index + + if (!/(38|40|27)/.test(e.keyCode)) return + + $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + if (!isActive || (isActive && e.keyCode == 27)) return $this.click() + + $items = $('[role=menu] li:not(.divider) a', $parent) + + if (!$items.length) return + + index = $items.index($items.filter(':focus')) + + if (e.keyCode == 38 && index > 0) index-- // up + if (e.keyCode == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items + .eq(index) + .focus() + } + + } + + function clearMenus() { + getParent($(toggle)) + .removeClass('open') + } + + function getParent($this) { + var selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.length || ($parent = $this.parent()) + + return $parent + } + + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('dropdown') + if (!data) $this.data('dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.dropdown.Constructor = Dropdown + + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + $(function () { + $('html') + .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus) + $('body') + .on('click.dropdown touchstart.dropdown.data-api', '.dropdown', function (e) { e.stopPropagation() }) + .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle) + .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/docs/static/js/bootstrap-modal.js b/docs/static/js/bootstrap-modal.js new file mode 100644 index 00000000..62fbc951 --- /dev/null +++ b/docs/static/js/bootstrap-modal.js @@ -0,0 +1,239 @@ +/* ========================================================= + * bootstrap-modal.js v2.1.0 + * http://twitter.github.com/bootstrap/javascript.html#modals + * ========================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function (element, options) { + this.options = options + this.$element = $(element) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) + this.options.remote && this.$element.find('.modal-body').load(this.options.remote) + } + + Modal.prototype = { + + constructor: Modal + + , toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + , e = $.Event('show') + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + $('body').addClass('modal-open') + + this.isShown = true + + this.escape() + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(document.body) //don't move modals dom position + } + + that.$element + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element + .addClass('in') + .attr('aria-hidden', false) + .focus() + + that.enforceFocus() + + transition ? + that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + } + + , hide: function (e) { + e && e.preventDefault() + + var that = this + + e = $.Event('hide') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + $('body').removeClass('modal-open') + + this.escape() + + $(document).off('focusin.modal') + + this.$element + .removeClass('in') + .attr('aria-hidden', true) + + $.support.transition && this.$element.hasClass('fade') ? + this.hideWithTransition() : + this.hideModal() + } + + , enforceFocus: function () { + var that = this + $(document).on('focusin.modal', function (e) { + if (that.$element[0] !== e.target && !that.$element.has(e.target).length) { + that.$element.focus() + } + }) + } + + , escape: function () { + var that = this + if (this.isShown && this.options.keyboard) { + this.$element.on('keyup.dismiss.modal', function ( e ) { + e.which == 27 && that.hide() + }) + } else if (!this.isShown) { + this.$element.off('keyup.dismiss.modal') + } + } + + , hideWithTransition: function () { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end) + that.hideModal() + }, 500) + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout) + that.hideModal() + }) + } + + , hideModal: function (that) { + this.$element + .hide() + .trigger('hidden') + + this.backdrop() + } + + , removeBackdrop: function () { + this.$backdrop.remove() + this.$backdrop = null + } + + , backdrop: function (callback) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('