diff options
Diffstat (limited to 'docs')
95 files changed, 6143 insertions, 735 deletions
diff --git a/docs/Makefile b/docs/Makefile index bdf48549a3..f6293a8e7f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -31,6 +31,7 @@ help: @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" + @echo " texinfo to make a Texinfo source file" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @@ -116,6 +117,11 @@ man: @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished; the Texinfo files are in $(BUILDDIR)/texinfo." + gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo diff --git a/docs/_theme/djangodocs/static/djangodocs.css b/docs/_theme/djangodocs/static/djangodocs.css index 4adb8387cc..4efb7e04f3 100644 --- a/docs/_theme/djangodocs/static/djangodocs.css +++ b/docs/_theme/djangodocs/static/djangodocs.css @@ -1,7 +1,7 @@ /*** setup ***/ html { background:#092e20;} body { font:12px/1.5 Verdana,sans-serif; background:#092e20; color: white;} -#custom-doc { width:76.54em;*width:74.69em;min-width:995px; max-width:100em; margin:auto; text-align:left; padding-top:16px; margin-top:0;} +#custom-doc { width:76.54em;*width:74.69em;min-width:995px; max-width:100em; margin:auto; text-align:left; padding-top:16px; margin-top:0;} #hd { padding: 4px 0 12px 0; } #bd { background:#234F32; } #ft { color:#487858; font-size:90%; padding-bottom: 2em; } @@ -54,7 +54,7 @@ hr { color:#ccc; background-color:#ccc; height:1px; border:0; } p, ul, dl { margin-top:.6em; margin-bottom:1em; padding-bottom: 0.1em;} #yui-main div.yui-b img { max-width: 50em; margin-left: auto; margin-right: auto; display: block; } caption { font-size:1em; font-weight:bold; margin-top:0.5em; margin-bottom:0.5em; margin-left: 2px; text-align: center; } -blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } +blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } strong { font-weight: bold; } em { font-style: italic; } ins { font-weight: bold; text-decoration: none; } @@ -111,6 +111,7 @@ dt .literal, table .literal { background:none; } .note, .admonition { padding-left:65px; background:url(docicons-note.png) .8em .8em no-repeat;} div.admonition-philosophy { padding-left:65px; background:url(docicons-philosophy.png) .8em .8em no-repeat;} div.admonition-behind-the-scenes { padding-left:65px; background:url(docicons-behindscenes.png) .8em .8em no-repeat;} +.admonition.warning { background:url(docicons-warning.png) .8em .8em no-repeat; border:1px solid #ffc83c;} /*** versoinadded/changes ***/ div.versionadded, div.versionchanged { } diff --git a/docs/_theme/djangodocs/static/docicons-warning.png b/docs/_theme/djangodocs/static/docicons-warning.png Binary files differnew file mode 100644 index 0000000000..031b3e782a --- /dev/null +++ b/docs/_theme/djangodocs/static/docicons-warning.png diff --git a/docs/conf.py b/docs/conf.py index 433fd679a1..f58e4ecb2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,12 +14,15 @@ from __future__ import unicode_literals import sys -import os +from os.path import abspath, dirname, join + +# Make sure we get the version of this copy of Django +sys.path.insert(1, dirname(dirname(abspath(__file__)))) # 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.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext"))) +sys.path.append(abspath(join(dirname(__file__), "_ext"))) # -- General configuration ----------------------------------------------------- @@ -52,11 +55,23 @@ copyright = 'Django Software Foundation and contributors' # built documents. # # The short X.Y version. -version = '1.5' +version = '1.6' # The full version, including alpha/beta/rc tags. -release = '1.5' +try: + from django import VERSION, get_version +except ImportError: + release = version +else: + def django_release(): + pep386ver = get_version() + if VERSION[3:5] == ('alpha', 0) and 'dev' not in pep386ver: + return pep386ver + '.dev' + return pep386ver + + release = django_release() + # The next version to be released -django_next_version = '1.6' +django_next_version = '1.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -233,6 +248,16 @@ man_pages = [ ] +# -- Options for Texinfo output ------------------------------------------------ + +# List of tuples (startdocname, targetname, title, author, dir_entry, +# description, category, toctree_only) +texinfo_documents=[( + master_doc, "django", "", "", "Django", + "Documentation of the Django framework", "Web development", False +)] + + # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index 872ad254c9..30d452cbe2 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -10,8 +10,8 @@ things: * Set the :setting:`SESSION_COOKIE_DOMAIN` setting in your admin config file to match your domain. For example, if you're going to - "http://www.example.com/admin/" in your browser, in - "myproject.settings" you should set ``SESSION_COOKIE_DOMAIN = 'www.example.com'``. + "http://www.example.com/admin/" in your browser, in "myproject.settings" you + should set :setting:`SESSION_COOKIE_DOMAIN` = 'www.example.com'. * Some browsers (Firefox?) don't like to accept cookies from domains that don't have dots in them. If you're running the admin site on "localhost" @@ -23,8 +23,9 @@ I can't log in. When I enter a valid username and password, it brings up the log ----------------------------------------------------------------------------------------------------------------------------------------------------------- If you're sure your username and password are correct, make sure your user -account has ``is_active`` and ``is_staff`` set to True. The admin site only -allows access to users with those two fields both set to True. +account has :attr:`~django.contrib.auth.models.User.is_active` and +:attr:`~django.contrib.auth.models.User.is_staff` set to True. The admin site +only allows access to users with those two fields both set to True. How can I prevent the cache middleware from caching the admin site? ------------------------------------------------------------------- @@ -64,9 +65,10 @@ My "list_filter" contains a ManyToManyField, but the filter doesn't display. Django won't bother displaying the filter for a ``ManyToManyField`` if there are fewer than two related objects. -For example, if your ``list_filter`` includes ``sites``, and there's only one -site in your database, it won't display a "Site" filter. In that case, -filtering by site would be meaningless. +For example, if your :attr:`~django.contrib.admin.ModelAdmin.list_filter` +includes :doc:`sites </ref/contrib/sites>`, and there's only one site in your +database, it won't display a "Site" filter. In that case, filtering by site +would be meaningless. Some objects aren't appearing in the admin. ------------------------------------------- @@ -85,9 +87,10 @@ How can I customize the functionality of the admin interface? You've got several options. If you want to piggyback on top of an add/change form that Django automatically generates, you can attach arbitrary JavaScript -modules to the page via the model's ``class Admin`` ``js`` parameter. That -parameter is a list of URLs, as strings, pointing to JavaScript modules that -will be included within the admin form via a ``<script>`` tag. +modules to the page via the model's class Admin :ref:`js parameter +<modeladmin-media-definitions>`. That parameter is a list of URLs, as strings, +pointing to JavaScript modules that will be included within the admin form via +a ``<script>`` tag. If you want more flexibility than simply tweaking the auto-generated forms, feel free to write custom views for the admin. The admin is powered by Django diff --git a/docs/faq/general.txt b/docs/faq/general.txt index 659a8c5ad9..dc569840d1 100644 --- a/docs/faq/general.txt +++ b/docs/faq/general.txt @@ -68,7 +68,7 @@ Who's behind this? Django was originally developed at World Online, the Web department of a newspaper in Lawrence, Kansas, USA. Django's now run by an international team of volunteers; you can read all about them over at the :doc:`list of committers -</internals/committers>` +</internals/committers>`. Which sites use Django? ----------------------- diff --git a/docs/faq/install.txt b/docs/faq/install.txt index a772a379d5..a92c9d87ea 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -18,7 +18,7 @@ What are Django's prerequisites? Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python libraries are required for basic Django usage. Django 1.5 also has -experimental support for Python 3.2 and above. +experimental support for Python 3.2.3 and above. For a development environment -- if you just want to experiment with Django -- you don't need to have a separate Web server installed; Django comes with its @@ -69,14 +69,14 @@ Django version Python versions 1.2 2.4, 2.5, 2.6, 2.7 1.3 2.4, 2.5, 2.6, 2.7 **1.4** **2.5, 2.6, 2.7** -*1.5 (future)* *2.6, 2.7* and *3.2, 3.3 (experimental)* +*1.5 (future)* *2.6, 2.7* and *3.2.3, 3.3 (experimental)* ============== =============== Can I use Django with Python 3? ------------------------------- -Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we -don't yet suggest that you use Django and Python 3 in production. +Django 1.5 introduces experimental support for Python 3.2.3 and above. However, +we don't yet suggest that you use Django and Python 3 in production. Python 3 support should be considered a "preview". It's offered to bootstrap the transition of the Django ecosystem to Python 3, and to help you start diff --git a/docs/faq/troubleshooting.txt b/docs/faq/troubleshooting.txt index f984be4bf5..da286cf4e2 100644 --- a/docs/faq/troubleshooting.txt +++ b/docs/faq/troubleshooting.txt @@ -5,6 +5,11 @@ Troubleshooting This page contains some advice about errors and problems commonly encountered during the development of Django applications. +.. _troubleshooting-django-admin-py: + +Problems running django-admin.py +================================ + "command not found: django-admin.py" ------------------------------------ @@ -13,4 +18,34 @@ installed Django via ``python setup.py``. If it's not on your path, you can find it in ``site-packages/django/bin``, where ``site-packages`` is a directory within your Python installation. Consider symlinking to :doc:`django-admin.py </ref/django-admin>` from some place on your path, such as -:file:`/usr/local/bin`.
\ No newline at end of file +:file:`/usr/local/bin`. + +Script name may differ in distribution packages +----------------------------------------------- + +If you installed Django using a Linux distribution's package manager +(e.g. ``apt-get`` or ``yum``) ``django-admin.py`` may have been renamed to +``django-admin``; use that instead. + +Mac OS X permissions +-------------------- + +If you're using Mac OS X, you may see the message "permission denied" when +you try to run ``django-admin.py``. This is because, on Unix-based systems like +OS X, a file must be marked as "executable" before it can be run as a program. +To do this, open Terminal.app and navigate (using the ``cd`` command) to the +directory where :doc:`django-admin.py </ref/django-admin>` is installed, then +run the command ``sudo chmod +x django-admin.py``. + +Running virtualenv on Windows +----------------------------- + +If you used virtualenv_ to :ref:`install Django <installing-official-release>` +on Windows, you may get an ``ImportError`` when you try to run +``django-admin.py``. This is because Windows does not run the +Python interpreter from your virtual environment unless you invoke it +directly. Instead, prefix all commands that use .py files with ``python`` and +use the full path to the file, like so: +``python C:\pythonXY\Scripts\django-admin.py``. + +.. _virtualenv: http://www.virtualenv.org/ diff --git a/docs/howto/custom-file-storage.txt b/docs/howto/custom-file-storage.txt index 5f1dae17ef..090cc089cb 100644 --- a/docs/howto/custom-file-storage.txt +++ b/docs/howto/custom-file-storage.txt @@ -27,9 +27,9 @@ You'll need to follow these steps: option = settings.CUSTOM_STORAGE_OPTIONS ... -#. Your storage class must implement the ``_open()`` and ``_save()`` methods, - along with any other methods appropriate to your storage class. See below for - more on these methods. +#. Your storage class must implement the :meth:`_open()` and :meth:`_save()` + methods, along with any other methods appropriate to your storage class. See + below for more on these methods. In addition, if your class provides local file storage, it must override the ``path()`` method. @@ -46,8 +46,7 @@ Your custom storage system may override any of the storage methods explained in You'll also usually want to use hooks specifically designed for custom storage objects. These are: -``_open(name, mode='rb')`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: _open(name, mode='rb') **Required**. @@ -56,8 +55,7 @@ uses to open the file. This must return a ``File`` object, though in most cases, you'll want to return some subclass here that implements logic specific to the backend storage system. -``_save(name, content)`` -~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: _save(name, content) Called by ``Storage.save()``. The ``name`` will already have gone through ``get_valid_name()`` and ``get_available_name()``, and the ``content`` will be a @@ -67,8 +65,8 @@ Should return the actual name of name of the file saved (usually the ``name`` passed in, but if the storage needs to change the file name return the new name instead). -``get_valid_name(name)`` -~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: get_valid_name(name) + Returns a filename suitable for use with the underlying storage system. The ``name`` argument passed to this method is the original filename sent to the @@ -78,8 +76,7 @@ how non-standard characters are converted to safe filenames. The code provided on ``Storage`` retains only alpha-numeric characters, periods and underscores from the original filename, removing everything else. -``get_available_name(name)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. method:: get_available_name(name) Returns a filename that is available in the storage mechanism, possibly taking the provided filename into account. The ``name`` argument passed to this method diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index 7f68485dff..ead04a4643 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -88,12 +88,20 @@ Using mod_wsgi daemon mode ========================== "Daemon mode" is the recommended mode for running mod_wsgi (on non-Windows -platforms). See the `official mod_wsgi documentation`_ for details on setting -up daemon mode. The only change required to the above configuration if you use -daemon mode is that you can't use ``WSGIPythonPath``; instead you should use -the ``python-path`` option to ``WSGIDaemonProcess``, for example:: +platforms). To create the required daemon process group and delegate the +Django instance to run in it, you will need to add appropriate +``WSGIDaemonProcess`` and ``WSGIProcessGroup`` directives. A further change +required to the above configuration if you use daemon mode is that you can't +use ``WSGIPythonPath``; instead you should use the ``python-path`` option to +``WSGIDaemonProcess``, for example:: WSGIDaemonProcess example.com python-path=/path/to/mysite.com:/path/to/venv/lib/python2.7/site-packages + WSGIProcessGroup example.com + +See the official mod_wsgi documentation for `details on setting up daemon +mode`_. + +.. _details on setting up daemon mode: http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Delegation_To_Daemon_Process .. _serving-files: diff --git a/docs/index.txt b/docs/index.txt index 5055edf7e7..9fea8ff3f2 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -44,7 +44,12 @@ Are you new to Django or to programming? This is the place to start! :doc:`Part 1 <intro/tutorial01>` | :doc:`Part 2 <intro/tutorial02>` | :doc:`Part 3 <intro/tutorial03>` | - :doc:`Part 4 <intro/tutorial04>` + :doc:`Part 4 <intro/tutorial04>` | + :doc:`Part 5 <intro/tutorial05>` + +* **Advanced Tutorials:** + :doc:`How to write reusable apps <intro/reusable-apps>` | + :doc:`Writing your first patch for Django <intro/contributing>` The model layer =============== diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index b51ac0d906..a90dc32605 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -52,8 +52,15 @@ and time availability), claim it by following these steps: page, 2. then click "Submit changes." +.. note:: + The Django software foundation requests that anyone contributing more than + a trivial patch to Django sign and submit a `Contributor License + Agreement`_, this ensures that the Django Software Foundation has clear + license to all contributions allowing for a clear license for all users. + .. _Create an account: https://www.djangoproject.com/accounts/register/ .. _password reset page: https://www.djangoproject.com/accounts/password/reset/ +.. _Contributor License Agreement: https://www.djangoproject.com/foundation/cla/ Ticket claimers' responsibility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index a828b06b36..4e702ff83e 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -9,7 +9,8 @@ The tests cover: * Models and the database API (``tests/modeltests``), * Everything else in core Django code (``tests/regressiontests``), -* :ref:`contrib-apps` (``django/contrib/<app>/tests``). +* :ref:`contrib-apps` (``django/contrib/<app>/tests`` or + ``tests/regressiontests/<app>_...``). We appreciate any and all contributions to the test suite! @@ -38,6 +39,8 @@ with this sample ``settings`` module, ``cd`` into the Django If you get an ``ImportError: No module named django.contrib`` error, you need to add your install of Django to your ``PYTHONPATH``. +.. _running-unit-tests-settings: + Using another ``settings`` module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -133,6 +136,8 @@ Then, run the tests normally, for example: ./runtests.py --settings=test_sqlite admin_inlines +.. _running-unit-tests-dependencies: + Running all the tests ~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 10bbfe1a91..414da30ff8 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -140,6 +140,11 @@ these changes. removed. In its place use :class:`~django.contrib.staticfiles.handlers.StaticFilesHandler`. +* The template tags library ``adminmedia`` and the template tag ``{% + admin_media_prefix %}`` will be removed in favor of the generic static files + handling. (This is faster than the usual deprecation path; see the + :doc:`Django 1.4 release notes</releases/1.4>`.) + * The :ttag:`url` and :ttag:`ssi` template tags will be modified so that the first argument to each tag is a template variable, not an implied string. In 1.4, this behavior is provided by a version of the tag @@ -232,10 +237,6 @@ these changes. :setting:`LOGGING` setting should include this filter explicitly if it is desired. -* The template tag - :func:`django.contrib.admin.templatetags.adminmedia.admin_media_prefix` - will be removed in favor of the generic static files handling. - * The builtin truncation functions :func:`django.utils.text.truncate_words` and :func:`django.utils.text.truncate_html_words` will be removed in favor of the ``django.utils.text.Truncator`` class. @@ -293,6 +294,14 @@ these changes. * The ``AUTH_PROFILE_MODULE`` setting, and the ``get_profile()`` method on the User model, will be removed. +* The ``cleanup`` management command will be removed. It's replaced by + ``clearsessions``. + +* The ``daily_cleanup.py`` script will be removed. + +* The ``depth`` keyword argument will be removed from + :meth:`~django.db.models.query.QuerySet.select_related`. + 2.0 --- diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt new file mode 100644 index 0000000000..a343814c02 --- /dev/null +++ b/docs/intro/contributing.txt @@ -0,0 +1,580 @@ +=================================== +Writing your first patch for Django +=================================== + +Introduction +============ + +Interested in giving back to the community a little? Maybe you've found a bug +in Django that you'd like to see fixed, or maybe there's a small feature you +want added. + +Contributing back to Django itself is the best way to see your own concerns +addressed. This may seem daunting at first, but it's really pretty simple. +We'll walk you through the entire process, so you can learn by example. + +Who's this tutorial for? +------------------------ + +For this tutorial, we expect that you have at least a basic understanding of +how Django works. This means you should be comfortable going through the +existing tutorials on :doc:`writing your first Django app</intro/tutorial01>`. +In addition, you should have a good understanding of Python itself. But if you +don't, `Dive Into Python`__ is a fantastic (and free) online book for beginning +Python programmers. + +Those of you who are unfamiliar with version control systems and Trac will find +that this tutorial and its links include just enough information to get started. +However, you'll probably want to read some more about these different tools if +you plan on contributing to Django regularly. + +For the most part though, this tutorial tries to explain as much as possible, +so that it can be of use to the widest audience. + +.. admonition:: Where to get help: + + If you're having trouble going through this tutorial, please post a message + to `django-developers`__ or drop by `#django-dev on irc.freenode.net`__ to + chat with other Django users who might be able to help. + +__ http://diveintopython.net/toc/index.html +__ http://groups.google.com/group/django-developers +__ irc://irc.freenode.net/django-dev + +What does this tutorial cover? +------------------------------ + +We'll be walking you through contributing a patch to Django for the first time. +By the end of this tutorial, you should have a basic understanding of both the +tools and the processes involved. Specifically, we'll be covering the following: + +* Installing Git. +* How to download a development copy of Django. +* Running Django's test suite. +* Writing a test for your patch. +* Writing the code for your patch. +* Testing your patch. +* Generating a patch file for your changes. +* Where to look for more information. + +Once you're done with the tutorial, you can look through the rest of +:doc:`Django's documentation on contributing</internals/contributing/index>`. +It contains lots of great information and is a must read for anyone who'd like +to become a regular contributor to Django. If you've got questions, it's +probably got the answers. + +Installing Git +============== + +For this tutorial, you'll need Git installed to download the current +development version of Django and to generate patch files for the changes you +make. + +To check whether or not you have Git installed, enter ``git`` into the command +line. If you get messages saying that this command could be found, you'll have +to download and install it, see `Git's download page`__. + +If you're not that familiar with Git, you can always find out more about its +commands (once it's installed) by typing ``git help`` into the command line. + +__ http://git-scm.com/download + +Getting a copy of Django's development version +============================================== + +The first step to contributing to Django is to get a copy of the source code. +From the command line, use the ``cd`` command to navigate to the directory +where you'll want your local copy of Django to live. + +Download the Django source code repository using the following command:: + + git clone https://github.com/django/django.git + +.. note:: + + For users who wish to use `virtualenv`__, you can use:: + + pip install -e /path/to/your/local/clone/django/ + + to link your cloned checkout into a virtual environment. This is a great + option to isolate your development copy of Django from the rest of your + system and avoids potential package conflicts. + +__ http://www.virtualenv.org + +Rolling back to a previous revision of Django +============================================= + +For this tutorial, we'll be using `ticket #17549`__ as a case study, so we'll +rewind Django's version history in git to before that ticket's patch was +applied. This will allow us to go through all of the steps involved in writing +that patch from scratch, including running Django's test suite. + +**Keep in mind that while we'll be using an older revision of Django's trunk +for the purposes of the tutorial below, you should always use the current +development revision of Django when working on your own patch for a ticket!** + +.. note:: + + The patch for this ticket was written by Ulrich Petri, and it was applied + to Django as `commit ac2052ebc84c45709ab5f0f25e685bf656ce79bc`__. + Consequently, we'll be using the revision of Django just prior to that, + `commit 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac`__. + +__ https://code.djangoproject.com/ticket/17549 +__ https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc +__ https://github.com/django/django/commit/39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac + +Navigate into Django's root directory (that's the one that contains ``django``, +``docs``, ``tests``, ``AUTHORS``, etc.). You can then check out the older +revision of Django that we'll be using in the tutorial below:: + + git checkout 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac + +Running Django's test suite for the first time +============================================== + +When contributing to Django it's very important that your code changes don't +introduce bugs into other areas of Django. One way to check that Django still +works after you make your changes is by running Django's test suite. If all +the tests still pass, then you can be reasonably sure that your changes +haven't completely broken Django. If you've never run Django's test suite +before, it's a good idea to run it once beforehand just to get familiar with +what its output is supposed to look like. + +We can run the test suite by simply ``cd``-ing into the Django ``tests/`` +directory and, if you're using GNU/Linux, Mac OS X or some other flavor of +Unix, run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite + +If you're on Windows, the above should work provided that you are using +"Git Bash" provided by the default Git install. GitHub has a `nice tutorial`__. + +__ https://help.github.com/articles/set-up-git#platform-windows + +.. note:: + + If you're using ``virtualenv``, you can omit ``PYTHONPATH=..`` when running + the tests. This instructs Python to look for Django in the parent directory + of ``tests``. ``virtualenv`` puts your copy of Django on the ``PYTHONPATH`` + automatically. + +Now sit back and relax. Django's entire test suite has over 4800 different +tests, so it can take anywhere from 5 to 15 minutes to run, depending on the +speed of your computer. + +While Django's test suite is running, you'll see a stream of characters +representing the status of each test as it's run. ``E`` indicates that an error +was raised during a test, and ``F`` indicates that a test's assertions failed. +Both of these are considered to be test failures. Meanwhile, ``x`` and ``s`` +indicated expected failures and skipped tests, respectively. Dots indicate +passing tests. + +Skipped tests are typically due to missing external libraries required to run +the test; see :ref:`running-unit-tests-dependencies` for a list of dependencies +and be sure to install any for tests related to the changes you are making (we +won't need any for this tutorial). + +Once the tests complete, you should be greeted with a message informing you +whether the test suite passed or failed. Since you haven't yet made any changes +to Django's code, the entire test suite **should** pass. If you get failures or +errors make sure you've followed all of the previous steps properly. See +:ref:`running-unit-tests` for more information. + +Note that the latest Django trunk may not always be stable. When developing +against trunk, you can check `Django's continuous integration builds`__ to +determine if the failures are specific to your machine or if they are also +present in Django's official builds. If you click to view a particular build, +you can view the "Configuration Matrix" which shows failures broken down by +Python version and database backend. + +__ http://ci.djangoproject.com/ + +.. note:: + + For this tutorial and the ticket we're working on, testing against SQLite + is sufficient, however, it's possible (and sometimes necessary) to + :ref:`run the tests using a different database + <running-unit-tests-settings>`. + +Writing some tests for your ticket +================================== + +In most cases, for a patch to be accepted into Django it has to include tests. +For bug fix patches, this means writing a regression test to ensure that the +bug is never reintroduced into Django later on. A regression test should be +written in such a way that it will fail while the bug still exists and pass +once the bug has been fixed. For patches containing new features, you'll need +to include tests which ensure that the new features are working correctly. +They too should fail when the new feature is not present, and then pass once it +has been implemented. + +A good way to do this is to write your new tests first, before making any +changes to the code. This style of development is called +`test-driven development`__ and can be applied to both entire projects and +single patches. After writing your tests, you then run them to make sure that +they do indeed fail (since you haven't fixed that bug or added that feature +yet). If your new tests don't fail, you'll need to fix them so that they do. +After all, a regression test that passes regardless of whether a bug is present +is not very helpful at preventing that bug from reoccurring down the road. + +Now for our hands-on example. + +__ http://en.wikipedia.org/wiki/Test-driven_development + +Writing some tests for ticket #17549 +------------------------------------ + +`Ticket #17549`__ describes the following, small feature addition: + + It's useful for URLField to give you a way to open the URL; otherwise you + might as well use a CharField. + +In order to resolve this ticket, we'll add a ``render`` method to the +``AdminURLFieldWidget`` in order to display a clickable link above the input +widget. Before we make those changes though, we're going to write a couple +tests to verify that our modification functions correctly and continues to +function correctly in the future. + +Navigate to Django's ``tests/regressiontests/admin_widgets/`` folder and +open the ``tests.py`` file. Add the following code on line 269 right before the +``AdminFileWidgetTest`` class:: + + class AdminURLWidgetTest(DjangoTestCase): + def test_render(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', '')), + '<input class="vURLField" name="test" type="text" />' + ) + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example.com')), + '<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>' + ) + + def test_render_idn(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example-äüö.com')), + '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>' + ) + + def test_render_quoting(self): + w = widgets.AdminURLFieldWidget() + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')), + '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>' + ) + self.assertHTMLEqual( + conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')), + '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>' + ) + +The new tests check to see that the ``render`` method we'll be adding works +correctly in a couple different situations. + +.. admonition:: But this testing thing looks kinda hard... + + If you've never had to deal with tests before, they can look a little hard + to write at first glance. Fortunately, testing is a *very* big subject in + computer programming, so there's lots of information out there: + + * A good first look at writing tests for Django can be found in the + documentation on :doc:`Testing Django applications</topics/testing/>`. + * Dive Into Python (a free online book for beginning Python developers) + includes a great `introduction to Unit Testing`__. + * After reading those, if you want something a little meatier to sink + your teeth into, there's always the `Python unittest documentation`__. + +__ https://code.djangoproject.com/ticket/17549 +__ http://diveintopython.net/unit_testing/index.html +__ http://docs.python.org/library/unittest.html + +Running your new test +--------------------- + +Remember that we haven't actually made any modifications to +``AdminURLFieldWidget`` yet, so our tests are going to fail. Let's run all the +tests in the ``model_forms_regress`` folder to make sure that's really what +happens. From the command line, ``cd`` into the Django ``tests/`` directory +and run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets + +If the tests ran correctly, you should see three failures corresponding to each +of the test methods we added. If all of the tests passed, then you'll want to +make sure that you added the new test shown above to the appropriate folder and +class. + +Writing the code for your ticket +================================ + +Next we'll be adding the functionality described in `ticket #17549`__ to Django. + +Writing the code for ticket #17549 +---------------------------------- + +Navigate to the ``django/django/contrib/admin/`` folder and open the +``widgets.py`` file. Find the ``AdminURLFieldWidget`` class on line 302 and add +the following ``render`` method after the existing ``__init__`` method:: + + def render(self, name, value, attrs=None): + html = super(AdminURLFieldWidget, self).render(name, value, attrs) + if value: + value = force_text(self._format_value(value)) + final_attrs = {'href': mark_safe(smart_urlquote(value))} + html = format_html( + '<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>', + _('Currently:'), flatatt(final_attrs), value, + _('Change:'), html + ) + return html + +Verifying your test now passes +------------------------------ + +Once you're done modifying Django, we need to make sure that the tests we wrote +earlier pass, so we can see whether the code we wrote above is working +correctly. To run the tests in the ``admin_widgets`` folder, ``cd`` into the +Django ``tests/`` directory and run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite admin_widgets + +Oops, good thing we wrote those tests! You should still see 3 failures with +the following exception:: + + NameError: global name 'smart_urlquote' is not defined + +We forgot to add the import for that method. Go ahead and add the +``smart_urlquote`` import at the end of line 13 of +``django/contrib/admin/widgets.py`` so it looks as follows:: + + from django.utils.html import escape, format_html, format_html_join, smart_urlquote + +Re-run the tests and everything should pass. If it doesn't, make sure you +correctly modified the ``AdminURLFieldWidget`` class as shown above and +copied the new tests correctly. + +__ https://code.djangoproject.com/ticket/17549 + +Running Django's test suite for the second time +=============================================== + +Once you've verified that your patch and your test are working correctly, it's +a good idea to run the entire Django test suite just to verify that your change +hasn't introduced any bugs into other areas of Django. While successfully +passing the entire test suite doesn't guarantee your code is bug free, it does +help identify many bugs and regressions that might otherwise go unnoticed. + +To run the entire Django test suite, ``cd`` into the Django ``tests/`` +directory and run:: + + PYTHONPATH=.. python runtests.py --settings=test_sqlite + +As long as you don't see any failures, you're good to go. Note that this fix +also made a `small CSS change`__ to format the new widget. You can make the +change if you'd like, but we'll skip it for now in the interest of brevity. + +__ https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc#diff-0 + +Writing Documentation +===================== + +This is a new feature, so it should be documented. Add the following on line +925 of ``django/docs/ref/models/fields.txt`` beneath the existing docs for +``URLField``:: + + .. versionadded:: 1.5 + + The current value of the field will be displayed as a clickable link above the + input widget. + +For more information on writing documentation, including an explanation of what +the ``versionadded`` bit is all about, see +:doc:`/internals/contributing/writing-documentation`. That page also includes +an explanation of how to build a copy of the documentation locally, so you can +preview the HTML that will be generated. + +Generating a patch for your changes +=================================== + +Now it's time to generate a patch file that can be uploaded to Trac or applied +to another copy of Django. To get a look at the content of your patch, run the +following command:: + + git diff + +This will display the differences between your current copy of Django (with +your changes) and the revision that you initially checked out earlier in the +tutorial. + +Once you're done looking at the patch, hit the ``q`` key to exit back to the +command line. If the patch's content looked okay, you can run the following +command to save the patch file to your current working directory:: + + git diff > 17549.diff + +You should now have a file in the root Django directory called ``17549.diff``. +This patch file contains all your changes and should look this: + +.. code-block:: diff + + diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py + index 1e0bc2d..9e43a10 100644 + --- a/django/contrib/admin/widgets.py + +++ b/django/contrib/admin/widgets.py + @@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static + from django.core.urlresolvers import reverse + from django.forms.widgets import RadioFieldRenderer + from django.forms.util import flatatt + -from django.utils.html import escape, format_html, format_html_join + +from django.utils.html import escape, format_html, format_html_join, smart_urlquote + from django.utils.text import Truncator + from django.utils.translation import ugettext as _ + from django.utils.safestring import mark_safe + @@ -306,6 +306,18 @@ class AdminURLFieldWidget(forms.TextInput): + final_attrs.update(attrs) + super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) + + + def render(self, name, value, attrs=None): + + html = super(AdminURLFieldWidget, self).render(name, value, attrs) + + if value: + + value = force_text(self._format_value(value)) + + final_attrs = {'href': mark_safe(smart_urlquote(value))} + + html = format_html( + + '<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>', + + _('Currently:'), flatatt(final_attrs), value, + + _('Change:'), html + + ) + + return html + + + class AdminIntegerFieldWidget(forms.TextInput): + class_name = 'vIntegerField' + + diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt + index 809d56e..d44f85f 100644 + --- a/docs/ref/models/fields.txt + +++ b/docs/ref/models/fields.txt + @@ -922,6 +922,10 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional + :attr:`~CharField.max_length`argument. If you don't specify + :attr:`~CharField.max_length`, a default of 200 is used. + + +.. versionadded:: 1.5 + + + +The current value of the field will be displayed as a clickable link above the + +input widget. + + Relationship fields + =================== + diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py + index 4b11543..94acc6d 100644 + --- a/tests/regressiontests/admin_widgets/tests.py + +++ b/tests/regressiontests/admin_widgets/tests.py + @@ -265,6 +265,35 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase): + '<p class="datetime">Datum: <input value="01.12.2007" type="text" class="vDateField" name="test_0" size="10" /><br />Zeit: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>', + ) + + +class AdminURLWidgetTest(DjangoTestCase): + + def test_render(self): + + w = widgets.AdminURLFieldWidget() + + self.assertHTMLEqual( + + conditional_escape(w.render('test', '')), + + '<input class="vURLField" name="test" type="text" />' + + ) + + self.assertHTMLEqual( + + conditional_escape(w.render('test', 'http://example.com')), + + '<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>' + + ) + + + + def test_render_idn(self): + + w = widgets.AdminURLFieldWidget() + + self.assertHTMLEqual( + + conditional_escape(w.render('test', 'http://example-äüö.com')), + + '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>' + + ) + + + + def test_render_quoting(self): + + w = widgets.AdminURLFieldWidget() + + self.assertHTMLEqual( + + conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')), + + '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>' + + ) + + self.assertHTMLEqual( + + conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')), + + '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>' + + ) + + class AdminFileWidgetTest(DjangoTestCase): + def test_render(self): + +So what do I do next? +===================== + +Congratulations, you've generated your very first Django patch! Now that you've +got that under your belt, you can put those skills to good use by helping to +improve Django's codebase. Generating patches and attaching them to Trac +tickets is useful, however, since we are using git - adopting a more :doc:`git +oriented workflow </internals/contributing/writing-code/working-with-git>` is +recommended. + +Since we never committed our changes locally, perform the following to get your +git branch back to a good starting point:: + + git reset --hard HEAD + git checkout master + +More information for new contributors +------------------------------------- + +Before you get too into writing patches for Django, there's a little more +information on contributing that you should probably take a look at: + +* You should make sure to read Django's documentation on + :doc:`claiming tickets and submitting patches + </internals/contributing/writing-code/submitting-patches>`. + It covers Trac etiquette, how to claim tickets for yourself, expected + coding style for patches, and many other important details. +* First time contributors should also read Django's :doc:`documentation + for first time contributors</internals/contributing/new-contributors/>`. + It has lots of good advice for those of us who are new to helping out + with Django. +* After those, if you're still hungry for more information about + contributing, you can always browse through the rest of + :doc:`Django's documentation on contributing</internals/contributing/index>`. + It contains a ton of useful information and should be your first source + for answering any questions you might have. + +Finding your first real ticket +------------------------------ + +Once you've looked through some of that information, you'll be ready to go out +and find a ticket of your own to write a patch for. Pay special attention to +tickets with the "easy pickings" criterion. These tickets are often much +simpler in nature and are great for first time contributors. Once you're +familiar with contributing to Django, you can move on to writing patches for +more difficult and complicated tickets. + +If you just want to get started already (and nobody would blame you!), try +taking a look at the list of `easy tickets that need patches`__ and the +`easy tickets that have patches which need improvement`__. If you're familiar +with writing tests, you can also look at the list of +`easy tickets that need tests`__. Just remember to follow the guidelines about +claiming tickets that were mentioned in the link to Django's documentation on +:doc:`claiming tickets and submitting patches +</internals/contributing/writing-code/submitting-patches>`. + +__ https://code.djangoproject.com/query?status=new&status=reopened&has_patch=0&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority +__ https://code.djangoproject.com/query?status=new&status=reopened&needs_better_patch=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority +__ https://code.djangoproject.com/query?status=new&status=reopened&needs_tests=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority + +What's next? +------------ + +After a ticket has a patch, it needs to be reviewed by a second set of eyes. +After uploading a patch or submitting a pull request, be sure to update the +ticket metadata by setting the flags on the ticket to say "has patch", +"doesn't need tests", etc, so others can find it for review. Contributing +doesn't necessarily always mean writing a patch from scratch. Reviewing +existing patches is also a very helpful contribution. See +:doc:`/internals/contributing/triaging-tickets` for details. diff --git a/docs/intro/index.txt b/docs/intro/index.txt index 19290a53c6..ea6a3c4d29 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -6,31 +6,34 @@ place: read this material to quickly get up and running. .. toctree:: :maxdepth: 1 - + overview install tutorial01 tutorial02 tutorial03 tutorial04 + tutorial05 + reusable-apps whatsnext - + contributing + .. seealso:: If you're new to Python_, you might want to start by getting an idea of what the language is like. Django is 100% Python, so if you've got minimal comfort with Python you'll probably get a lot more out of Django. - + If you're new to programming entirely, you might want to start with this `list of Python resources for non-programmers`_ - + If you already know a few other languages and want to get up to speed with Python quickly, we recommend `Dive Into Python`_ (also available in a `dead-tree version`_). If that's not quite your style, there are quite a few other `books about Python`_. - + .. _python: http://python.org/ .. _list of Python resources for non-programmers: http://wiki.python.org/moin/BeginnersGuide/NonProgrammers .. _dive into python: http://diveintopython.net/ .. _dead-tree version: http://www.amazon.com/exec/obidos/ASIN/1590593561/ref=nosim/jacobian20 - .. _books about Python: http://wiki.python.org/moin/PythonBooks
\ No newline at end of file + .. _books about Python: http://wiki.python.org/moin/PythonBooks diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 4d5a86f82b..ba49e3ccf2 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -257,8 +257,7 @@ lookup and function calls. Note ``{{ article.pub_date|date:"F j, Y" }}`` uses a Unix-style "pipe" (the "|" character). This is called a template filter, and it's a way to filter the value of a variable. In this case, the date filter formats a Python datetime object in -the given format (as found in PHP's date function; yes, there is one good idea -in PHP). +the given format (as found in PHP's date function). You can chain together as many filters as you'd like. You can write custom filters. You can write custom template tags, which run custom Python code behind diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt new file mode 100644 index 0000000000..6aade4997e --- /dev/null +++ b/docs/intro/reusable-apps.txt @@ -0,0 +1,365 @@ +============================================= +Advanced tutorial: How to write reusable apps +============================================= + +This advanced tutorial begins where :doc:`Tutorial 5 </intro/tutorial05>` left +off. We'll be turning our Web-poll into a standalone Python package you can +reuse in new projects and share with other people. + +If you haven't recently completed Tutorials 1–5, we encourage you to review +these so that your example project matches the one described below. + +Reusability matters +=================== + +It's a lot of work to design, build, test and maintain a web application. Many +Python and Django projects share common problems. Wouldn't it be great if we +could save some of this repeated work? + +Reusability is the way of life in Python. `The Python Package Index (PyPI) +<http://guide.python-distribute.org/contributing.html#pypi-info>`_ has a vast +range of packages you can use in your own Python programs. Check out `Django +Packages <http://www.djangopackages.com>`_ for existing reusable apps you could +incorporate in your project. Django itself is also just a Python package. This +means that you can take existing Python packages or Django apps and compose +them into your own web project. You only need to write the parts that make +your project unique. + +Let's say you were starting a new project that needed a polls app like the one +we've been working on. How do you make this app reusable? Luckily, you're well +on the way already. In :doc:`Tutorial 3 </intro/tutorial03>`, we saw how we +could decouple polls from the project-level URLconf using an ``include``. +In this tutorial, we'll take further steps to make the app easy to use in new +projects and ready to publish for others to install and use. + +.. admonition:: Package? App? + + A Python `package <http://docs.python.org/tutorial/modules.html#packages>`_ + provides a way of grouping related Python code for easy reuse. A package + contains one or more files of Python code (also known as "modules"). + + A package can be imported with ``import foo.bar`` or ``from foo import + bar``. For a directory (like ``polls``) to form a package, it must contain + a special file ``__init__.py``, even if this file is empty. + + A Django *app* is just a Python package that is specifically intended for + use in a Django project. An app may also use common Django conventions, + such as having a ``models.py`` file. + + Later on we use the term *packaging* to describe the process of making a + Python package easy for others to install. It can be a little confusing, we + know. + +Completing your reusable app +============================ + +After the previous tutorials, our project should look like this:: + + mysite/ + manage.py + mysite/ + __init__.py + settings.py + urls.py + wsgi.py + polls/ + admin.py + __init__.py + models.py + tests.py + urls.py + views.py + +You also have a directory somewhere called ``mytemplates`` which you created in +:doc:`Tutorial 2 </intro/tutorial02>`. You specified its location in the +TEMPLATE_DIRS setting. This directory should look like this:: + + mytemplates/ + admin/ + base_site.html + polls/ + detail.html + index.html + results.html + +The polls app is already a Python package, thanks to the ``polls/__init__.py`` +file. That's a great start, but we can't just pick up this package and drop it +into a new project. The polls templates are currently stored in the +project-wide ``mytemplates`` directory. To make the app self-contained, it +should also contain the necessary templates. + +Inside the ``polls`` app, create a new ``templates`` directory. Now move the +``polls`` template directory from ``mytemplates`` into the new +``templates``. Your project should now look like this:: + + mysite/ + manage.py + mysite/ + __init__.py + settings.py + urls.py + wsgi.py + polls/ + admin.py + __init__.py + models.py + templates/ + polls/ + detail.html + index.html + results.html + tests.py + urls.py + views.py + +Your project-wide templates directory should now look like this:: + + mytemplates/ + admin/ + base_site.html + +Looking good! Now would be a good time to confirm that your polls application +still works correctly. How does Django know how to find the new location of +the polls templates even though we didn't modify :setting:`TEMPLATE_DIRS`? +Django has a :setting:`TEMPLATE_LOADERS` setting which contains a list +of callables that know how to import templates from various sources. One of +the defaults is :class:`django.template.loaders.app_directories.Loader` which +looks for a "templates" subdirectory in each of the :setting:`INSTALLED_APPS`. + +The ``polls`` directory could now be copied into a new Django project and +immediately reused. It's not quite ready to be published though. For that, we +need to package the app to make it easy for others to install. + +.. admonition:: Why nested? + + Why create a ``polls`` directory under ``templates`` when we're + already inside the polls app? This directory is needed to avoid conflicts in + Django's ``app_directories`` template loader. For example, if two + apps had a template called ``base.html``, without the extra directory it + wouldn't be possible to distinguish between the two. It's a good convention + to use the name of your app for this directory. + +.. _installing-reusable-apps-prerequisites: + +Installing some prerequisites +============================= + +The current state of Python packaging is a bit muddled with various tools. For +this tutorial, we're going to use distribute_ to build our package. It's a +community-maintained fork of the older ``setuptools`` project. We'll also be +using `pip`_ to uninstall it after we're finished. You should install these +two packages now. If you need help, you can refer to :ref:`how to install +Django with pip<installing-official-release>`. You can install ``distribute`` +the same way. + +.. _distribute: http://pypi.python.org/pypi/distribute +.. _pip: http://pypi.python.org/pypi/pip + +Packaging your app +================== + +Python *packaging* refers to preparing your app in a specific format that can +be easily installed and used. Django itself is packaged very much like +this. For a small app like polls, this process isn't too difficult. + +1. First, create a parent directory for ``polls``, outside of your Django + project. Call this directory ``django-polls``. + +.. admonition:: Choosing a name for your app + + When choosing a name for your package, check resources like PyPI to avoid + naming conflicts with existing packages. It's often useful to prepend + ``django-`` to your module name when creating a package to distribute. + This helps others looking for Django apps identify your app as Django + specific. + +2. Move the ``polls`` directory into the ``django-polls`` directory. + +3. Create a file ``django-polls/README.txt`` with the following contents:: + + ===== + Polls + ===== + + Polls is a simple Django app to conduct Web-based polls. For each + question, visitors can choose between a fixed number of answers. + + Detailed documentation is in the "docs" directory. + + Quick start + ----------- + + 1. Add "polls" to your INSTALLED_APPS setting like this:: + + INSTALLED_APPS = ( + ... + 'polls', + ) + + 2. Include the polls URLconf in your project urls.py like this:: + + url(r'^polls/', include('polls.urls')), + + 3. Run `python manage.py syncdb` to create the polls models. + + 4. Start the development server and visit http://127.0.0.1:8000/admin/ + to create a poll (you'll need the Admin app enabled). + + 5. Visit http://127.0.0.1:8000/polls/ to participate in the poll. + +4. Create a ``django-polls/LICENSE`` file. Choosing a license is beyond the +scope of this tutorial, but suffice it to say that code released publicly +without a license is *useless*. Django and many Django-compatible apps are +distributed under the BSD license; however, you're free to pick your own +license. Just be aware that your licensing choice will affect who is able +to use your code. + +5. Next we'll create a ``setup.py`` file which provides details about how to +build and install the app. A full explanation of this file is beyond the +scope of this tutorial, but the `distribute docs +<http://packages.python.org/distribute/setuptools.html>`_ have a good explanation. +Create a file ``django-polls/setup.py`` with the following contents:: + + import os + from setuptools import setup + + README = open(os.path.join(os.path.dirname(__file__), 'README.txt')).read() + + # allow setup.py to be run from any path + os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) + + setup( + name = 'django-polls', + version = '0.1', + packages = ['polls'], + include_package_data = True, + license = 'BSD License', # example license + description = 'A simple Django app to conduct Web-based polls.', + long_description = README, + url = 'http://www.example.com/', + author = 'Your Name', + author_email = 'yourname@example.com', + classifiers = [ + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', # example license + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], + ) + +.. admonition:: I thought you said we were going to use ``distribute``? + + Distribute is a drop-in replacement for ``setuptools``. Even though we + appear to import from ``setuptools``, since we have ``distribute`` + installed, it will override the import. + +6. Only Python modules and packages are included in the package by default. To + include additional files, we'll need to create a ``MANIFEST.in`` file. The + distribute docs referred to in the previous step discuss this file in more + details. To include the templates and our LICENSE file, create a file + ``django-polls/MANIFEST.in`` with the following contents:: + + include LICENSE + recursive-include polls/templates * + +7. It's optional, but recommended, to include detailed documentation with your + app. Create an empty directory ``django-polls/docs`` for future + documentation. Add an additional line to ``django-polls/MANIFEST.in``:: + + recursive-include docs * + + Note that the ``docs`` directory won't be included in your package unless + you add some files to it. Many Django apps also provide their documentation + online through sites like `readthedocs.org <http://readthedocs.org>`_. + +8. Try building your package with ``python setup.py sdist`` (run from inside + ``django-polls``). This creates a directory called ``dist`` and builds your + new package, ``django-polls-0.1.tar.gz``. + +For more information on packaging, see `The Hitchhiker's Guide to Packaging +<http://guide.python-distribute.org/quickstart.html>`_. + +Using your own package +====================== + +Since we moved the ``polls`` directory out of the project, it's no longer +working. We'll now fix this by installing our new ``django-polls`` package. + +.. admonition:: Installing as a user library + + The following steps install ``django-polls`` as a user library. Per-user + installs have a lot of advantages over installing the package system-wide, + such as being usable on systems where you don't have administrator access + as well as preventing the package from affecting system services and other + users of the machine. Python 2.6 added support for user libraries, so if + you are using an older version this won't work, but Django 1.5 requires + Python 2.6 or newer anyway. + + Note that per-user installations can still affect the behavior of system + tools that run as that user, so ``virtualenv`` is a more robust solution + (see below). + +1. Inside ``django-polls/dist``, untar the new package + ``django-polls-0.1.tar.gz`` (e.g. ``tar xzvf django-polls-0.1.tar.gz``). If + you're using Windows, you can download the command-line tool bsdtar_ to do + this, or you can use a GUI-based tool such as 7-zip_. + +2. Change into the directory created in step 1 (e.g. ``cd django-polls-0.1``). + +3. If you're using GNU/Linux, Mac OS X or some other flavor of Unix, enter the + command ``python setup.py install --user`` at the shell prompt. If you're + using Windows, start up a command shell and run the command + ``setup.py install --user``. + + With luck, your Django project should now work correctly again. Run the + server again to confirm this. + +4. To uninstall the package, use pip (you already :ref:`installed it + <installing-reusable-apps-prerequisites>`, right?):: + + pip uninstall django-polls + +.. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm +.. _7-zip: http://www.7-zip.org/ +.. _pip: http://pypi.python.org/pypi/pip + +Publishing your app +=================== + +Now that we've packaged and tested ``django-polls``, it's ready to share with +the world! If this wasn't just an example, you could now: + +* Email the package to a friend. + +* Upload the package on your Web site. + +* Post the package on a public repository, such as `The Python Package Index + (PyPI) <http://guide.python-distribute.org/contributing.html#pypi-info>`_. + +For more information on PyPI, see the `Quickstart +<http://guide.python-distribute.org/quickstart.html#register-your-package-with-the-python-package-index-pypi>`_ +section of The Hitchhiker's Guide to Packaging. One detail this guide mentions +is choosing the license under which your code is distributed. + +Installing Python packages with virtualenv +========================================== + +Earlier, we installed the polls app as a user library. This has some +disadvantages: + +* Modifying the user libraries can affect other Python software on your system. + +* You won't be able to run multiple versions of this package (or others with + the same name). + +Typically, these situations only arise once you're maintaining several Django +projects. When they do, the best solution is to use `virtualenv +<http://www.virtualenv.org/>`_. This tool allows you to maintain multiple +isolated Python environments, each with its own copy of the libraries and +package namespace. diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 1e2231d1e0..9419f9c4eb 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -52,25 +52,8 @@ code, then run the following command: django-admin.py startproject mysite -This will create a ``mysite`` directory in your current directory. If it didn't -work, see :doc:`Troubleshooting </faq/troubleshooting>`. - -.. admonition:: Script name may differ in distribution packages - - If you installed Django using a Linux distribution's package manager - (e.g. apt-get or yum) ``django-admin.py`` may have been renamed to - ``django-admin``. You may continue through this documentation by omitting - ``.py`` from each command. - -.. admonition:: Mac OS X permissions - - If you're using Mac OS X, you may see the message "permission denied" when - you try to run ``django-admin.py startproject``. This is because, on - Unix-based systems like OS X, a file must be marked as "executable" before it - can be run as a program. To do this, open Terminal.app and navigate (using - the ``cd`` command) to the directory where :doc:`django-admin.py - </ref/django-admin>` is installed, then run the command - ``sudo chmod +x django-admin.py``. +This will create a ``mysite`` directory in your current directory. If it didn't +work, see :ref:`troubleshooting-django-admin-py`. .. note:: @@ -81,12 +64,12 @@ work, see :doc:`Troubleshooting </faq/troubleshooting>`. .. admonition:: Where should this code live? - If your background is in PHP, you're probably used to putting code under the - Web server's document root (in a place such as ``/var/www``). With Django, - you don't do that. It's not a good idea to put any of this Python code - within your Web server's document root, because it risks the possibility - that people may be able to view your code over the Web. That's not good for - security. + If your background is in plain old PHP (with no use of modern frameworks), + you're probably used to putting code under the Web server's document root + (in a place such as ``/var/www``). With Django, you don't do that. It's + not a good idea to put any of this Python code within your Web server's + document root, because it risks the possibility that people may be able + to view your code over the Web. That's not good for security. Put your code in some directory **outside** of the document root, such as :file:`/home/mycode`. @@ -220,8 +203,8 @@ your database connection settings. SQLite). * :setting:`HOST` -- The host your database is on. Leave this as - an empty string if your database server is on the same physical - machine (not used for SQLite). + an empty string (or possibly ``127.0.0.1``) if your database server is on the + same physical machine (not used for SQLite). See :setting:`HOST` for details. If you're new to databases, we recommend simply using SQLite by setting :setting:`ENGINE` to ``'django.db.backends.sqlite3'`` and :setting:`NAME` to @@ -666,6 +649,7 @@ Save these changes and start a new Python interactive shell by running >>> Poll.objects.get(pub_date__year=2012) <Poll: What's up?> + # Request an ID that doesn't exist, this will raise an exception. >>> Poll.objects.get(id=2) Traceback (most recent call last): ... diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index b87b280d7c..2c8d25ae6f 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -445,11 +445,6 @@ live anywhere on your filesystem that Django can access. (Django runs as whatever user your server runs.) However, keeping your templates within the project is a good convention to follow. -When you’ve done that, create a directory polls in your template directory. -Within that, create a file called index.html. Note that our -``loader.get_template('polls/index.html')`` code from above maps to -[template_directory]/polls/index.html” on the filesystem. - By default, :setting:`TEMPLATE_DIRS` is empty. So, let's add a line to it, to tell Django where our templates live:: diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 169e6cd59f..3159ee88c2 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -235,11 +235,11 @@ be used to identify the matched pattern; and ``\d+`` is a regular expression to match a sequence of digits (i.e., a number). Because the URL patterns are regular expressions, there really is no limit on -what you can do with them. And there's no need to add URL cruft such as ``.php`` --- unless you have a sick sense of humor, in which case you can do something -like this:: +what you can do with them. And there's no need to add URL cruft such as +``.html`` -- unless you want to, in which case you can do something like +this:: - (r'^polls/latest\.php$', 'polls.views.index'), + (r'^polls/latest\.html$', 'polls.views.index'), But, don't do that. It's silly. @@ -315,6 +315,12 @@ Load the page in your Web browser, and you should see a bulleted-list containing the "What's up" poll from Tutorial 1. The link points to the poll's detail page. +.. admonition:: Organizing Templates + + Rather than one big templates directory, you can also store templates + within each app. We'll discuss this in more detail in the :doc:`reusable + apps tutorial</intro/reusable-apps>`. + A shortcut: :func:`~django.shortcuts.render` -------------------------------------------- diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index 8909caf98b..1619b599bb 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -275,8 +275,5 @@ Run the server, and use your new polling app based on generic views. For full details on generic views, see the :doc:`generic views documentation </topics/class-based-views/index>`. -What's next? -============ - -The tutorial ends here for the time being. In the meantime, you might want to -check out some pointers on :doc:`where to go from here </intro/whatsnext>`. +When you're comfortable with forms and generic views, read :doc:`part 5 of this +tutorial</intro/tutorial05>` to learn about testing our polls app. diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt new file mode 100644 index 0000000000..163b7cdd0f --- /dev/null +++ b/docs/intro/tutorial05.txt @@ -0,0 +1,650 @@ +===================================== +Writing your first Django app, part 5 +===================================== + +This tutorial begins where :doc:`Tutorial 4 </intro/tutorial04>` left off. +We've built a Web-poll application, and we'll now create some automated tests +for it. + +Introducing automated testing +============================= + +What are automated tests? +------------------------- + +Tests are simple routines that check the operation of your code. + +Testing operates at different levels. Some tests might apply to a tiny detail +- *does a particular model method return values as expected?*, while others +examine the overall operation of the software - *does a sequence of user inputs +on the site produce the desired result?* That's no different from the kind of +testing you did earlier in :doc:`Tutorial 1 </intro/tutorial01>`, using the +shell to examine the behavior of a method, or running the application and +entering data to check how it behaves. + +What's different in *automated* tests is that the testing work is done for +you by the system. You create a set of tests once, and then as you make changes +to your app, you can check that your code still works as you originally +intended, without having to perform time consuming manual testing. + +Why you need to create tests +---------------------------- + +So why create tests, and why now? + +You may feel that you have quite enough on your plate just learning +Python/Django, and having yet another thing to learn and do may seem +overwhelming and perhaps unnecessary. After all, our polls application is +working quite happily now; going through the trouble of creating automated +tests is not going to make it work any better. If creating the polls +application is the last bit of Django programming you will ever do, then true, +you don't need to know how to create automated tests. But, if that's not the +case, now is an excellent time to learn. + +Tests will save you time +~~~~~~~~~~~~~~~~~~~~~~~~ + +Up to a certain point, 'checking that it seems to work' will be a satisfactory +test. In a more sophisticated application, you might have dozens of complex +interactions between components. + +A change in any of those components could have unexpected consequences on the +application's behavior. Checking that it still 'seems to work' could mean +running through your code's functionality with twenty different variations of +your test data just to make sure you haven't broken something - not a good use +of your time. + +That's especially true when automated tests could do this for you in seconds. +If something's gone wrong, tests will also assist in identifying the code +that's causing the unexpected behavior. + +Sometimes it may seem a chore to tear yourself away from your productive, +creative programming work to face the unglamorous and unexciting business +of writing tests, particularly when you know your code is working properly. + +However, the task of writing tests is a lot more fulfilling than spending hours +testing your application manually or trying to identify the cause of a +newly-introduced problem. + +Tests don't just identify problems, they prevent them +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's a mistake to think of tests merely as a negative aspect of development. + +Without tests, the purpose or intended behavior of an application might be +rather opaque. Even when it's your own code, you will sometimes find yourself +poking around in it trying to find out what exactly it's doing. + +Tests change that; they light up your code from the inside, and when something +goes wrong, they focus light on the part that has gone wrong - *even if you +hadn't even realized it had gone wrong*. + +Tests make your code more attractive +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You might have created a brilliant piece of software, but you will find that +many other developers will simply refuse to look at it because it lacks tests; +without tests, they won't trust it. Jacob Kaplan-Moss, one of Django's +original developers, says "Code without tests is broken by design." + +That other developers want to see tests in your software before they take it +seriously is yet another reason for you to start writing tests. + +Tests help teams work together +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The previous points are written from the point of view of a single developer +maintaining an application. Complex applications will be maintained by teams. +Tests guarantee that colleagues don't inadvertently break your code (and that +you don't break theirs without knowing). If you want to make a living as a +Django programmer, you must be good at writing tests! + +Basic testing strategies +======================== + +There are many ways to approach writing tests. + +Some programmers follow a discipline called "`test-driven development`_"; they +actually write their tests before they write their code. This might seem +counter-intuitive, but in fact it's similar to what most people will often do +anyway: they describe a problem, then create some code to solve it. Test-driven +development simply formalizes the problem in a Python test case. + +More often, a newcomer to testing will create some code and later decide that +it should have some tests. Perhaps it would have been better to write some +tests earlier, but it's never too late to get started. + +Sometimes it's difficult to figure out where to get started with writing tests. +If you have written several thousand lines of Python, choosing something to +test might not be easy. In such a case, it's fruitful to write your first test +the next time you make a change, either when you add a new feature or fix a bug. + +So let's do that right away. + +.. _test-driven development: http://en.wikipedia.org/wiki/Test-driven_development/ + +Writing our first test +====================== + +We identify a bug +----------------- + +Fortunately, there's a little bug in the ``polls`` application for us to fix +right away: the ``Poll.was_published_recently()`` method returns ``True`` if +the ``Poll`` was published within the last day (which is correct) but also if +the ``Poll``'s ``pub_date`` field is in the future (which certainly isn't). + +You can see this in the Admin; create a poll whose date lies in the future; +you'll see that the ``Poll`` change list claims it was published recently. + +You can also see this using the shell:: + + >>> import datetime + >>> from django.utils import timezone + >>> from polls.models import Poll + >>> # create a Poll instance with pub_date 30 days in the future + >>> future_poll = Poll(pub_date=timezone.now() + datetime.timedelta(days=30)) + >>> # was it published recently? + >>> future_poll.was_published_recently() + True + +Since things in the future are not 'recent', this is clearly wrong. + +Create a test to expose the bug +------------------------------- + +What we've just done in the shell to test for the problem is exactly what we +can do in an automated test, so let's turn that into an automated test. + +The best place for an application's tests is in the application's ``tests.py`` +file - the testing system will look there for tests automatically. + +Put the following in the ``tests.py`` file in the ``polls`` application (you'll +notice ``tests.py`` contains some dummy tests, you can remove those):: + + import datetime + + from django.utils import timezone + from django.test import TestCase + + from polls.models import Poll + + class PollMethodTests(TestCase): + + def test_was_published_recently_with_future_poll(self): + """ + was_published_recently() should return False for polls whose + pub_date is in the future + """ + future_poll = Poll(pub_date=timezone.now() + datetime.timedelta(days=30)) + self.assertEqual(future_poll.was_published_recently(), False) + +What we have done here is created a :class:`django.test.TestCase` subclass +with a method that creates a ``Poll`` instance with a ``pub_date`` in the +future. We then check the output of ``was_published_recently()`` - which +*ought* to be False. + +Running tests +------------- + +In the terminal, we can run our test:: + + python manage.py test polls + +and you'll see something like:: + + Creating test database for alias 'default'... + F + ====================================================================== + FAIL: test_was_published_recently_with_future_poll (polls.tests.PollMethodTests) + ---------------------------------------------------------------------- + Traceback (most recent call last): + File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_poll + self.assertEqual(future_poll.was_published_recently(), False) + AssertionError: True != False + + ---------------------------------------------------------------------- + Ran 1 test in 0.001s + + FAILED (failures=1) + Destroying test database for alias 'default'... + +What happened is this: + +* ``python manage.py test polls`` looked for tests in the ``polls`` application + +* it found a subclass of the :class:`django.test.TestCase` class + +* it created a special database for the purpose of testing + +* it looked for test methods - ones whose names begin with ``test`` + +* in ``test_was_published_recently_with_future_poll`` it created a ``Poll`` + instance whose ``pub_date`` field is 30 days in the future + +* ... and using the ``assertEqual()`` method, it discovered that its + ``was_published_recently()`` returns ``True``, though we wanted it to return + ``False`` + +The test informs us which test failed and even the line on which the failure +occurred. + +Fixing the bug +-------------- + +We already know what the problem is: ``Poll.was_published_recently()`` should +return ``False`` if its ``pub_date`` is in the future. Amend the method in +``models.py``, so that it will only return ``True`` if the date is also in the +past:: + + def was_published_recently(self): + now = timezone.now() + return now - datetime.timedelta(days=1) <= self.pub_date < now + +and run the test again:: + + Creating test database for alias 'default'... + . + ---------------------------------------------------------------------- + Ran 1 test in 0.001s + + OK + Destroying test database for alias 'default'... + +After identifying a bug, we wrote a test that exposes it and corrected the bug +in the code so our test passes. + +Many other things might go wrong with our application in the future, but we can +be sure that we won't inadvertently reintroduce this bug, because simply +running the test will warn us immediately. We can consider this little portion +of the application pinned down safely forever. + +More comprehensive tests +------------------------ + +While we're here, we can further pin down the ``was_published_recently()`` +method; in fact, it would be positively embarrassing if in fixing one bug we had +introduced another. + +Add two more test methods to the same class, to test the behavior of the method +more comprehensively:: + + def test_was_published_recently_with_old_poll(self): + """ + was_published_recently() should return False for polls whose pub_date + is older than 1 day + """ + old_poll = Poll(pub_date=timezone.now() - datetime.timedelta(days=30)) + self.assertEqual(old_poll.was_published_recently(), False) + + def test_was_published_recently_with_recent_poll(self): + """ + was_published_recently() should return True for polls whose pub_date + is within the last day + """ + recent_poll = Poll(pub_date=timezone.now() - datetime.timedelta(hours=1)) + self.assertEqual(recent_poll.was_published_recently(), True) + +And now we have three tests that confirm that ``Poll.was_published_recently()`` +returns sensible values for past, recent, and future polls. + +Again, ``polls`` is a simple application, but however complex it grows in the +future and whatever other code it interacts with, we now have some guarantee +that the method we have written tests for will behave in expected ways. + +Test a view +=========== + +The polls application is fairly undiscriminating: it will publish any poll, +including ones whose ``pub_date`` field lies in the future. We should improve +this. Setting a ``pub_date`` in the future should mean that the Poll is +published at that moment, but invisible until then. + +A test for a view +----------------- + +When we fixed the bug above, we wrote the test first and then the code to fix +it. In fact that was a simple example of test-driven development, but it +doesn't really matter in which order we do the work. + +In our first test, we focused closely on the internal behavior of the code. For +this test, we want to check its behavior as it would be experienced by a user +through a web browser. + +Before we try to fix anything, let's have a look at the tools at our disposal. + +The Django test client +---------------------- + +Django provides a test :class:`~django.test.client.Client` to simulate a user +interacting with the code at the view level. We can use it in ``tests.py`` +or even in the shell. + +We will start again with the shell, where we need to do a couple of things that +won't be necessary in ``tests.py``. The first is to set up the test environment +in the shell:: + + >>> from django.test.utils import setup_test_environment + >>> setup_test_environment() + +Next we need to import the test client class (later in ``tests.py`` we will use +the :class:`django.test.TestCase` class, which comes with its own client, so +this won't be required):: + + >>> from django.test.client import Client + >>> # create an instance of the client for our use + >>> client = Client() + +With that ready, we can ask the client to do some work for us:: + + >>> # get a response from '/' + >>> response = client.get('/') + >>> # we should expect a 404 from that address + >>> response.status_code + 404 + >>> # on the other hand we should expect to find something at '/polls/' + >>> # we'll use 'reverse()' rather than a harcoded URL + >>> from django.core.urlresolvers import reverse + >>> response = client.get(reverse('polls:index')) + >>> response.status_code + 200 + >>> response.content + '\n\n\n <p>No polls are available.</p>\n\n' + >>> # note - you might get unexpected results if your ``TIME_ZONE`` + >>> # in ``settings.py`` is not correct. If you need to change it, + >>> # you will also need to restart your shell session + >>> from polls.models import Poll + >>> from django.utils import timezone + >>> # create a Poll and save it + >>> p = Poll(question="Who is your favorite Beatle?", pub_date=timezone.now()) + >>> p.save() + >>> # check the response once again + >>> response = client.get('/polls/') + >>> response.content + '\n\n\n <ul>\n \n <li><a href="/polls/1/">Who is your favorite Beatle?</a></li>\n \n </ul>\n\n' + >>> response.context['latest_poll_list'] + [<Poll: Who is your favorite Beatle?>] + +Improving our view +------------------ + +The list of polls shows polls that aren't published yet (i.e. those that have a +``pub_date`` in the future). Let's fix that. + +In :doc:`Tutorial 4 </intro/tutorial04>` we deleted the view functions from +``views.py`` in favor of a :class:`~django.views.generic.list.ListView` in +``urls.py``:: + + url(r'^$', + ListView.as_view( + queryset=Poll.objects.order_by('-pub_date')[:5], + context_object_name='latest_poll_list', + template_name='polls/index.html'), + name='index'), + +``response.context_data['latest_poll_list']`` extracts the data this view +places into the context. + +We need to amend the line that gives us the ``queryset``:: + + queryset=Poll.objects.order_by('-pub_date')[:5], + +Let's change the queryset so that it also checks the date by comparing it with +``timezone.now()``. First we need to add an import:: + + from django.utils import timezone + +and then we must amend the existing ``url`` function to:: + + url(r'^$', + ListView.as_view( + queryset=Poll.objects.filter(pub_date__lte=timezone.now) \ + .order_by('-pub_date')[:5], + context_object_name='latest_poll_list', + template_name='polls/index.html'), + name='index'), + +``Poll.objects.filter(pub_date__lte=timezone.now)`` returns a queryset +containing Polls whose ``pub_date`` is less than or equal to - that is, earlier +than or equal to - ``timezone.now``. Notice that we use a callable queryset +argument, ``timezone.now``, which will be evaluated at request time. If we had +included the parentheses, ``timezone.now()`` would be evaluated just once when +the web server is started. + +Testing our new view +-------------------- + +Now you can satisfy yourself that this behaves as expected by firing up the +runserver, loading the site in your browser, creating ``Polls`` with dates in +the past and future, and checking that only those that have been published are +listed. You don't want to have to do that *every single time you make any +change that might affect this* - so let's also create a test, based on our +shell session above. + +Add the following to ``polls/tests.py``:: + + from django.core.urlresolvers import reverse + +and we'll create a factory method to create polls as well as a new test class:: + + def create_poll(question, days): + """ + Creates a poll with the given `question` published the given number of + `days` offset to now (negative for polls published in the past, + positive for polls that have yet to be published). + """ + return Poll.objects.create(question=question, + pub_date=timezone.now() + datetime.timedelta(days=days)) + + class PollViewTests(TestCase): + def test_index_view_with_no_polls(self): + """ + If no polls exist, an appropriate message should be displayed. + """ + response = self.client.get(reverse('polls:index')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "No polls are available.") + self.assertQuerysetEqual(response.context['latest_poll_list'], []) + + def test_index_view_with_a_past_poll(self): + """ + Polls with a pub_date in the past should be displayed on the index page. + """ + create_poll(question="Past poll.", days=-30) + response = self.client.get(reverse('polls:index')) + self.assertQuerysetEqual( + response.context['latest_poll_list'], + ['<Poll: Past poll.>'] + ) + + def test_index_view_with_a_future_poll(self): + """ + Polls with a pub_date in the future should not be displayed on the + index page. + """ + create_poll(question="Future poll.", days=30) + response = self.client.get(reverse('polls:index')) + self.assertContains(response, "No polls are available.", status_code=200) + self.assertQuerysetEqual(response.context['latest_poll_list'], []) + + def test_index_view_with_future_poll_and_past_poll(self): + """ + Even if both past and future polls exist, only past polls should be + displayed. + """ + create_poll(question="Past poll.", days=-30) + create_poll(question="Future poll.", days=30) + response = self.client.get(reverse('polls:index')) + self.assertQuerysetEqual( + response.context['latest_poll_list'], + ['<Poll: Past poll.>'] + ) + + def test_index_view_with_two_past_polls(self): + """ + The polls index page may display multiple polls. + """ + create_poll(question="Past poll 1.", days=-30) + create_poll(question="Past poll 2.", days=-5) + response = self.client.get(reverse('polls:index')) + self.assertQuerysetEqual( + response.context['latest_poll_list'], + ['<Poll: Past poll 2.>', '<Poll: Past poll 1.>'] + ) + +Let's look at some of these more closely. + +First is a poll factory method, ``create_poll``, to take some repetition out +of the process of creating polls. + +``test_index_view_with_no_polls`` doesn't create any polls, but checks the +message: "No polls are available." and verifies the ``latest_poll_list`` is +empty. Note that the :class:`django.test.TestCase` class provides some +additional assertion methods. In these examples, we use +:meth:`~django.test.TestCase.assertContains()` and +:meth:`~django.test.TestCase.assertQuerysetEqual()`. + +In ``test_index_view_with_a_past_poll``, we create a poll and verify that it +appears in the list. + +In ``test_index_view_with_a_future_poll``, we create a poll with a ``pub_date`` +in the future. The database is reset for each test method, so the first poll is +no longer there, and so again the index shouldn't have any polls in it. + +And so on. In effect, we are using the tests to tell a story of admin input +and user experience on the site, and checking that at every state and for every +new change in the state of the system, the expected results are published. + +Testing the ``DetailView`` +-------------------------- + +What we have works well; however, even though future polls don't appear in the +*index*, users can still reach them if they know or guess the right URL. So we +need similar constraints in the ``DetailViews``, by adding:: + + queryset=Poll.objects.filter(pub_date__lte=timezone.now) + +to them - for example:: + + url(r'^(?P<pk>\d+)/$', + DetailView.as_view( + queryset=Poll.objects.filter(pub_date__lte=timezone.now), + model=Poll, + template_name='polls/detail.html'), + name='detail'), + +and of course, we will add some tests, to check that a ``Poll`` whose +``pub_date`` is in the past can be displayed, and that one with a ``pub_date`` +in the future is not:: + + class PollIndexDetailTests(TestCase): + def test_detail_view_with_a_future_poll(self): + """ + The detail view of a poll with a pub_date in the future should + return a 404 not found. + """ + future_poll = create_poll(question='Future poll.', days=5) + response = self.client.get(reverse('polls:detail', args=(future_poll.id,))) + self.assertEqual(response.status_code, 404) + + def test_detail_view_with_a_past_poll(self): + """ + The detail view of a poll with a pub_date in the past should display + the poll's question. + """ + past_poll = create_poll(question='Past Poll.', days=-5) + response = self.client.get(reverse('polls:detail', args=(past_poll.id,))) + self.assertContains(response, past_poll.question, status_code=200) + +Ideas for more tests +-------------------- + +We ought to add similar ``queryset`` arguments to the other ``DetailView`` +URLs, and create a new test class for each view. They'll be very similar to +what we have just created; in fact there will be a lot of repetition. + +We could also improve our application in other ways, adding tests along the +way. For example, it's silly that ``Polls`` can be published on the site that +have no ``Choices``. So, our views could check for this, and exclude such +``Polls``. Our tests would create a ``Poll`` without ``Choices`` and then test +that it's not published, as well as create a similar ``Poll`` *with* +``Choices``, and test that it *is* published. + +Perhaps logged-in admin users should be allowed to see unpublished ``Polls``, +but not ordinary visitors. Again: whatever needs to be added to the software to +accomplish this should be accompanied by a test, whether you write the test +first and then make the code pass the test, or work out the logic in your code +first and then write a test to prove it. + +At a certain point you are bound to look at your tests and wonder whether your +code is suffering from test bloat, which brings us to: + +When testing, more is better +============================ + +It might seem that our tests are growing out of control. At this rate there will +soon be more code in our tests than in our application, and the repetition +is unaesthetic, compared to the elegant conciseness of the rest of our code. + +**It doesn't matter**. Let them grow. For the most part, you can write a test +once and then forget about it. It will continue performing its useful function +as you continue to develop your program. + +Sometimes tests will need to be updated. Suppose that we amend our views so that +only ``Polls`` with ``Choices`` are published. In that case, many of our +existing tests will fail - *telling us exactly which tests need to be amended to +bring them up to date*, so to that extent tests help look after themselves. + +At worst, as you continue developing, you might find that you have some tests +that are now redundant. Even that's not a problem; in testing redundancy is +a *good* thing. + +As long as your tests are sensibly arranged, they won't become unmanageable. +Good rules-of-thumb include having: + +* a separate ``TestClass`` for each model or view +* a separate test method for each set of conditions you want to test +* test method names that describe their function + +Further testing +=============== + +This tutorial only introduces some of the basics of testing. There's a great +deal more you can do, and a number of very useful tools at your disposal to +achieve some very clever things. + +For example, while our tests here have covered some of the internal logic of a +model and the way our views publish information, you can use an "in-browser" +framework such as Selenium_ to test the way your HTML actually renders in a +browser. These tools allow you to check not just the behavior of your Django +code, but also, for example, of your JavaScript. It's quite something to see +the tests launch a browser, and start interacting with your site, as if a human +being were driving it! Django includes :class:`~django.test.LiveServerTestCase` +to facilitate integration with tools like Selenium. + +If you have a complex application, you may want to run tests automatically +with every commit for the purposes of `continuous integration`_, so that +quality control is itself - at least partially - automated. + +A good way to spot untested parts of your application is to check code +coverage. This also helps identify fragile or even dead code. If you can't test +a piece of code, it usually means that code should be refactored or removed. +Coverage will help to identify dead code. See +:ref:`topics-testing-code-coverage` for details. + +:doc:`Testing Django applications </topics/testing>` has comprehensive +information about testing. + +.. _Selenium: http://seleniumhq.org/ +.. _continuous integration: http://en.wikipedia.org/wiki/Continuous_integration + +What's next? +============ + +The beginner tutorial ends here for the time being. In the meantime, you might +want to check out some pointers on :doc:`where to go from here +</intro/whatsnext>`. + +If you are familiar with Python packaging and interested in learning how to +turn polls into a "reusable app", check out :doc:`Advanced tutorial: How to +write reusable apps</intro/reusable-apps>`. diff --git a/docs/make.bat b/docs/make.bat index d6299521eb..d7f54b2059 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -29,6 +29,7 @@ if "%1" == "help" ( echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages + echo. texinfo to make a Texinfo source file echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity @@ -143,6 +144,14 @@ if "%1" == "man" ( goto end ) +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + if "%%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 diff --git a/docs/misc/design-philosophies.txt b/docs/misc/design-philosophies.txt index c4fb582df9..0101da9586 100644 --- a/docs/misc/design-philosophies.txt +++ b/docs/misc/design-philosophies.txt @@ -203,9 +203,6 @@ We see a template system as a tool that controls presentation and presentation-related logic -- and that's it. The template system shouldn't support functionality that goes beyond this basic goal. -If we wanted to put everything in templates, we'd be using PHP. Been there, -done that, wised up. - Discourage redundancy --------------------- diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt index cbce3690e2..aa2f51f156 100644 --- a/docs/ref/class-based-views/flattened-index.txt +++ b/docs/ref/class-based-views/flattened-index.txt @@ -116,6 +116,7 @@ ListView * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` @@ -290,6 +291,7 @@ ArchiveIndexView * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` @@ -325,6 +327,7 @@ YearArchiveView * :attr:`~django.views.generic.dates.BaseYearArchiveView.make_object_list` [:meth:`~django.views.generic.dates.BaseYearArchiveView.get_make_object_list`] * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` @@ -363,6 +366,7 @@ MonthArchiveView * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] * :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` @@ -401,6 +405,7 @@ WeekArchiveView * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.list.MultipleObjectMixin.model` * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` @@ -443,6 +448,7 @@ DayArchiveView * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] * :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` @@ -487,6 +493,7 @@ TodayArchiveView * :attr:`~django.views.generic.dates.MonthMixin.month` [:meth:`~django.views.generic.dates.MonthMixin.get_month`] * :attr:`~django.views.generic.dates.MonthMixin.month_format` [:meth:`~django.views.generic.dates.MonthMixin.get_month_format`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_by` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_by`] +* :attr:`~django.views.generic.list.MultipleObjectMixin.paginate_orphans` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_paginate_orphans`] * :attr:`~django.views.generic.list.MultipleObjectMixin.paginator_class` * :attr:`~django.views.generic.list.MultipleObjectMixin.queryset` [:meth:`~django.views.generic.list.MultipleObjectMixin.get_queryset`] * :attr:`~django.views.generic.base.TemplateResponseMixin.response_class` diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index c6af23e421..0ae0bcdf42 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -7,6 +7,21 @@ Generic date views Date-based generic views, provided in :mod:`django.views.generic.dates`, are views for displaying drilldown pages for date-based data. +.. note:: + + Some of the examples on this page assume that an ``Article`` model has been + defined as follows in ``myapp/models.py``:: + + from django.db import models + from django.core.urlresolvers import reverse + + class Article(models.Model): + title = models.CharField(max_length=200) + pub_date = models.DateField() + + def get_absolute_url(self): + return reverse('article-detail', kwargs={'pk': self.pk}) + ArchiveIndexView ---------------- @@ -35,6 +50,31 @@ ArchiveIndexView month or day using the attribute ``date_list_period``. This also applies to all subclass views. + **Example views.py**:: + + from django.conf.urls import patterns, url + from django.views.generic.dates import ArchiveIndexView + + from myapp.models import Article + + urlpatterns = patterns('', + url(r'^archive/$', + ArchiveIndexView.as_view(model=Article, date_field="pub_date"), + name="article_archive"), + ) + + **Example myapp/article_archive.html**: + + .. code-block:: html+django + + <ul> + {% for article in latest %} + <li>{{ article.pub_date }}: {{ article.title }}</li> + {% endfor %} + </ul> + + This will output all articles. + YearArchiveView --------------- @@ -109,6 +149,49 @@ YearArchiveView * Uses a default ``template_name_suffix`` of ``_archive_year``. + **Example views.py**:: + + from django.views.generic.dates import YearArchiveView + + from myapp.models import Article + + class ArticleYearArchiveView(YearArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + make_object_list = True + allow_future = True + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import ArticleYearArchiveView + + urlpatterns = patterns('', + url(r'^(?P<year>\d{4})/$', + ArticleYearArchiveView.as_view(), + name="article_year_archive"), + ) + + **Example myapp/article_archive_year.html**: + + .. code-block:: html+django + + <ul> + {% for date in date_list %} + <li>{{ date|date }}</li> + {% endfor %} + </ul> + + <div> + <h1>All Articles for {{ year|date:"Y" }}</h1> + {% for obj in object_list %} + <p> + {{ obj.title }} - {{ obj.pub_date|date:"F j, Y" }} + </p> + {% endfor %} + </div> + MonthArchiveView ---------------- @@ -162,6 +245,54 @@ MonthArchiveView * Uses a default ``template_name_suffix`` of ``_archive_month``. + **Example views.py**:: + + from django.views.generic.dates import MonthArchiveView + + from myapp.models import Article + + class ArticleMonthArchiveView(MonthArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + make_object_list = True + allow_future = True + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import ArticleMonthArchiveView + + urlpatterns = patterns('', + # Example: /2012/aug/ + url(r'^(?P<year>\d{4})/(?P<month>[-\w]+)/$', + ArticleMonthArchiveView.as_view(), + name="archive_month"), + # Example: /2012/08/ + url(r'^(?P<year>\d{4})/(?P<month>\d+)/$', + ArticleMonthArchiveView.as_view(month_format='%m'), + name="archive_month_numeric"), + ) + + **Example myapp/article_archive_month.html**: + + .. code-block:: html+django + + <ul> + {% for article in object_list %} + <li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li> + {% endfor %} + </ul> + + <p> + {% if previous_month %} + Previous Month: {{ previous_month|date:"F Y" }} + {% endif %} + {% if next_month %} + Next Month: {{ next_month|date:"F Y" }} + {% endif %} + </p> + WeekArchiveView --------------- @@ -208,6 +339,65 @@ WeekArchiveView * Uses a default ``template_name_suffix`` of ``_archive_week``. + **Example views.py**:: + + from django.views.generic.dates import WeekArchiveView + + from myapp.models import Article + + class ArticleWeekArchiveView(WeekArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + make_object_list = True + week_format = "%W" + allow_future = True + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import ArticleWeekArchiveView + + urlpatterns = patterns('', + # Example: /2012/week/23/ + url(r'^(?P<year>\d{4})/week/(?P<week>\d+)/$', + ArticleWeekArchiveView.as_view(), + name="archive_week"), + ) + + **Example myapp/article_archive_week.html**: + + .. code-block:: html+django + + <h1>Week {{ week|date:'W' }}</h1> + + <ul> + {% for article in object_list %} + <li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li> + {% endfor %} + </ul> + + <p> + {% if previous_week %} + Previous Week: {{ previous_week|date:"F Y" }} + {% endif %} + {% if previous_week and next_week %}--{% endif %} + {% if next_week %} + Next week: {{ next_week|date:"F Y" }} + {% endif %} + </p> + + In this example, you are outputting the week number. The default + ``week_format`` in the ``WeekArchiveView`` uses week format ``'%U'`` + which is based on the United States week system where the week begins on a + Sunday. The ``'%W'`` format uses the ISO week format and its week + begins on a Monday. The ``'%W'`` format is the same in both the + :func:`~time.strftime` and the :tfilter:`date`. + + However, the :tfilter:`date` template filter does not have an equivalent + output format that supports the US based week system. The :tfilter:`date` + filter ``'%U'`` outputs the number of seconds since the Unix epoch. + DayArchiveView -------------- @@ -265,6 +455,53 @@ DayArchiveView * Uses a default ``template_name_suffix`` of ``_archive_day``. + **Example views.py**:: + + from django.views.generic.dates import DayArchiveView + + from myapp.models import Article + + class ArticleDayArchiveView(DayArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + make_object_list = True + allow_future = True + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import ArticleDayArchiveView + + urlpatterns = patterns('', + # Example: /2012/nov/10/ + url(r'^(?P<year>\d{4})/(?P<month>[-\w]+)/(?P<day>\d+)/$', + ArticleDayArchiveView.as_view(), + name="archive_day"), + ) + + **Example myapp/article_archive_day.html**: + + .. code-block:: html+django + + <h1>{{ day }}</h1> + + <ul> + {% for article in object_list %} + <li>{{ article.pub_date|date:"F j, Y" }}: {{ article.title }}</li> + {% endfor %} + </ul> + + <p> + {% if previous_day %} + Previous Day: {{ previous_day }} + {% endif %} + {% if previous_day and next_day %}--{% endif %} + {% if next_day %} + Next Day: {{ next_day }} + {% endif %} + </p> + TodayArchiveView ---------------- @@ -289,6 +526,40 @@ TodayArchiveView * :class:`django.views.generic.dates.DateMixin` * :class:`django.views.generic.base.View` + **Notes** + + * Uses a default ``template_name_suffix`` of ``_archive_today``. + + **Example views.py**:: + + from django.views.generic.dates import TodayArchiveView + + from myapp.models import Article + + class ArticleTodayArchiveView(TodayArchiveView): + queryset = Article.objects.all() + date_field = "pub_date" + make_object_list = True + allow_future = True + + **Example urls.py**:: + + from django.conf.urls import patterns, url + + from myapp.views import ArticleTodayArchiveView + + urlpatterns = patterns('', + url(r'^today/$', + ArticleTodayArchiveView.as_view(), + name="archive_today"), + ) + + .. admonition:: Where is the example template for ``TodayArchiveView``? + + This view uses by default the same template as the + :class:`~DayArchiveView`, which is in the previous example. If you need + a different template, set the ``template_name`` attribute to be the + name of the new template. DateDetailView -------------- @@ -313,6 +584,32 @@ DateDetailView * :class:`django.views.generic.detail.SingleObjectMixin` * :class:`django.views.generic.base.View` + **Context** + + * Includes the single object associated with the ``model`` specified in + the ``DateDetailView``. + + **Notes** + + * Uses a default ``template_name_suffix`` of ``_detail``. + + **Example urls.py**:: + + from django.conf.urls import patterns, url + from django.views.generic.dates import DateDetailView + + urlpatterns = patterns('', + url(r'^(?P<year>\d+)/(?P<month>[-\w]+)/(?P<day>\d+)/(?P<pk>\d+)/$', + DateDetailView.as_view(model=Article, date_field="pub_date"), + name="archive_date_detail"), + ) + + **Example myapp/article_detail.html**: + + .. code-block:: html+django + + <h1>{{ object.title }}</h1> + .. note:: All of the generic views listed above have matching ``Base`` views that @@ -332,5 +629,3 @@ DateDetailView .. class:: BaseTodayArchiveView .. class:: BaseDateDetailView - - diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 2fac06ee02..7ce5c1d1be 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -12,12 +12,11 @@ editing content: .. note:: - Some of the examples on this page assume that a model titled 'Author' - has been defined. For these cases we assume the following has been defined - in `myapp/models.py`:: + Some of the examples on this page assume that an ``Article`` model has been + defined as follows in ``myapp/models.py``:: - from django import models from django.core.urlresolvers import reverse + from django.db import models class Author(models.Model): name = models.CharField(max_length=200) diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index c4b632604a..a027953416 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -37,10 +37,11 @@ A class-based view is deployed into a URL pattern using the is modified, the actions of one user visiting your view could have an effect on subsequent users visiting the same view. -Any argument passed into :meth:`~django.views.generic.base.View.as_view()` will +Arguments passed into :meth:`~django.views.generic.base.View.as_view()` will be assigned onto the instance that is used to service a request. Using the previous example, this means that every request on ``MyView`` is able to use -``self.size``. +``self.size``. Arguments must correspond to attributes that already exist on +the class (return ``True`` on a ``hasattr`` check). Base vs Generic views --------------------- diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index cdb743fcbd..c85c962bce 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -69,8 +69,27 @@ MultipleObjectMixin An integer specifying how many objects should be displayed per page. If this is given, the view will paginate objects with :attr:`MultipleObjectMixin.paginate_by` objects per page. The view will - expect either a ``page`` query string parameter (via ``GET``) or a - ``page`` variable specified in the URLconf. + expect either a ``page`` query string parameter (via ``request.GET``) + or a ``page`` variable specified in the URLconf. + + .. attribute:: paginate_orphans + + .. versionadded:: 1.6 + + An integer specifying the number of "overflow" objects the last page + can contain. This extends the :attr:`MultipleObjectMixin.paginate_by` + limit on the last page by up to + :attr:`MultipleObjectMixin.paginate_orphans`, in order to keep the last + page from having a very small number of objects. + + .. attribute:: page_kwarg + + .. versionadded:: 1.5 + + A string specifying the name to use for the page parameter. + The view will expect this prameter to be available either as a query + string parameter (via ``request.GET``) or as a kwarg variable specified + in the URLconf. Defaults to ``page``. .. attribute:: paginator_class @@ -110,6 +129,14 @@ MultipleObjectMixin Returns an instance of the paginator to use for this view. By default, instantiates an instance of :attr:`paginator_class`. + .. method:: get_paginate_by() + + .. versionadded:: 1.6 + + An integer specifying the number of "overflow" objects the last page + can contain. By default this simply returns the value of + :attr:`MultipleObjectMixin.paginate_orphans`. + .. method:: get_allow_empty() Return a boolean specifying whether to display the page if no objects diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index 3f3b52983b..d7eef623d5 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -140,6 +140,15 @@ That's really all there is to it! If you're itching to write your own actions, you now know enough to get started. The rest of this document just covers more advanced techniques. +Handling errors in actions +-------------------------- + +If there are foreseeable error conditions that may occur while running your +action, you should gracefully inform the user of the problem. This means +handling exceptions and and using +:meth:`django.contrib.admin.ModelAdmin.message_user` to display a user friendly +description of the problem in the response. + Advanced action techniques ========================== diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 6ed929cb7d..6f79e97a3c 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -26,9 +26,10 @@ There are seven steps in activating the Django admin site: in your :setting:`INSTALLED_APPS` list, add them. 3. Add ``django.contrib.messages.context_processors.messages`` to - :setting:`TEMPLATE_CONTEXT_PROCESSORS` and - :class:`~django.contrib.messages.middleware.MessageMiddleware` to - :setting:`MIDDLEWARE_CLASSES`. (These are both active by default, so + :setting:`TEMPLATE_CONTEXT_PROCESSORS` as well as + :class:`django.contrib.auth.middleware.AuthenticationMiddleware` and + :class:`django.contrib.messages.middleware.MessageMiddleware` to + :setting:`MIDDLEWARE_CLASSES`. (These are all active by default, so you only need to do this if you've manually tweaked the settings.) 4. Determine which of your application's models should be editable in the @@ -569,8 +570,8 @@ subclass:: .. image:: _images/users_changelist.png - ``list_filter`` should be a list of elements, where each element should be - of one of the following types: + ``list_filter`` should be a list or tuple of elements, where each element + should be of one of the following types: * a field name, where the specified field should be either a ``BooleanField``, ``CharField``, ``DateField``, ``DateTimeField``, @@ -815,15 +816,36 @@ subclass:: By default the admin shows all fields as editable. Any fields in this option (which should be a ``list`` or ``tuple``) will display its data - as-is and non-editable. This option behaves nearly identical to - :attr:`ModelAdmin.list_display`. Usage is the same, however, when you - specify :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` the - read-only fields must be present to be shown (they are ignored otherwise). + as-is and non-editable; they are also excluded from the + :class:`~django.forms.ModelForm` used for creating and editing. Note that + when specifying :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` + the read-only fields must be present to be shown (they are ignored + otherwise). If ``readonly_fields`` is used without defining explicit ordering through :attr:`ModelAdmin.fields` or :attr:`ModelAdmin.fieldsets` they will be added last after all editable fields. + A read-only field can not only display data from a model's field, it can + also display the output of a a model's method or a method of the + ``ModelAdmin`` class itself. This is very similar to the way + :attr:`ModelAdmin.list_display` behaves. This provides an easy way to use + the admin interface to provide feedback on the status of the objects being + edited, for example:: + + class PersonAdmin(ModelAdmin): + readonly_fields = ('address_report',) + + def address_report(self, instance): + return ", ".join(instance.get_full_address()) or \ + "<span class='errors'>I can't determine this address.</span>" + + # short_description functions like a model field's verbose_name + address_report.short_description = "Address" + # in this example, we have used HTML tags in the output + address_report.allow_tags = True + + .. attribute:: ModelAdmin.save_as Set ``save_as`` to enable a "save as" feature on admin change forms. @@ -1054,6 +1076,14 @@ templates used by the :class:`ModelAdmin` views: changelist that will be linked to the change view, as described in the :attr:`ModelAdmin.list_display_links` section. +.. method:: ModelAdmin.get_list_filter(self, request) + + .. versionadded:: 1.5 + + The ``get_list_filter`` method is given the ``HttpRequest`` and is expected + to return the same kind of sequence type as for the + :attr:`~ModelAdmin.list_filter` attribute. + .. method:: ModelAdmin.get_inline_instances(self, request, obj=None) .. versionadded:: 1.5 @@ -1213,10 +1243,39 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.get_changelist(self, request, **kwargs) - Returns the Changelist class to be used for listing. By default, + Returns the ``Changelist`` class to be used for listing. By default, ``django.contrib.admin.views.main.ChangeList`` is used. By inheriting this class you can change the behavior of the listing. +.. method:: ModelAdmin.get_changelist_form(self, request, **kwargs) + + Returns a :class:`~django.forms.ModelForm` class for use in the ``Formset`` + on the changelist page. To use a custom form, for example:: + + class MyForm(forms.ModelForm): + class Meta: + model = MyModel + + class MyModelAdmin(admin.ModelAdmin): + def get_changelist_form(self, request, **kwargs): + return MyForm + +.. method:: ModelAdmin.get_changelist_formset(self, request, **kwargs) + + Returns a :ref:`ModelFormSet <model-formsets>` class for use on the + changelist page if :attr:`~ModelAdmin.list_editable` is used. To use a + custom formset, for example:: + + from django.forms.models import BaseModelFormSet + + class MyAdminFormSet(BaseModelFormSet): + pass + + class MyModelAdmin(admin.ModelAdmin): + def get_changelist_formset(self, request, **kwargs): + kwargs['formset'] = MyAdminFormSet + return super(MyModelAdmin, self).get_changelist_formset(request, **kwargs) + .. method:: ModelAdmin.has_add_permission(self, request) Should return ``True`` if adding an object is permitted, ``False`` @@ -1252,11 +1311,19 @@ templates used by the :class:`ModelAdmin` views: return qs return qs.filter(author=request.user) -.. method:: ModelAdmin.message_user(request, message) +.. method:: ModelAdmin.message_user(request, message, level=messages.INFO, extra_tags='', fail_silently=False) + + Sends a message to the user using the :mod:`django.contrib.messages` + backend. See the :ref:`custom ModelAdmin example <custom-admin-action>`. + + .. versionadded:: 1.5 - Sends a message to the user. The default implementation creates a message - using the :mod:`django.contrib.messages` backend. See the - :ref:`custom ModelAdmin example <custom-admin-action>`. + Keyword arguments allow you to change the message level, add extra CSS + tags, or fail silently if the ``contrib.messages`` framework is not + installed. These keyword arguments match those for + :func:`django.contrib.messages.add_message`, see that function's + documentation for more details. One difference is that the level may be + passed as a string label in addition to integer/constant. .. method:: ModelAdmin.get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True) @@ -1326,6 +1393,8 @@ instances which allow you to easily customize the response data before rendering. For more details, see the :doc:`TemplateResponse documentation </ref/template-response>`. +.. _modeladmin-media-definitions: + ``ModelAdmin`` media definitions -------------------------------- @@ -1532,6 +1601,10 @@ The ``InlineModelAdmin`` class adds: Specifies whether or not inline objects can be deleted in the inline. Defaults to ``True``. +.. method:: InlineModelAdmin.get_formset(self, request, obj=None, **kwargs) + + Returns a ``BaseInlineFormSet`` class for use in admin add/change views. + See the example for :class:`ModelAdmin.get_formsets`. Working with a model with two or more foreign keys to the same parent model --------------------------------------------------------------------------- diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 1c6ff7c7ed..8275092d2f 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -11,12 +11,6 @@ Django includes a simple, yet customizable comments framework. The built-in comments framework can be used to attach comments to any model, so you can use it for comments on blog entries, photos, book chapters, or anything else. -.. note:: - - If you used to use Django's older (undocumented) comments framework, you'll - need to upgrade. See the :doc:`upgrade guide </ref/contrib/comments/upgrade>` - for instructions. - Quick start guide ================= @@ -209,7 +203,7 @@ default version of which is included with Django. Rendering a custom comment form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you want more control over the look and feel of the comment form, you use use +If you want more control over the look and feel of the comment form, you may use :ttag:`get_comment_form` to get a :doc:`form object </topics/forms/index>` that you can use in the template:: @@ -350,7 +344,6 @@ More information models settings signals - upgrade custom forms moderation diff --git a/docs/ref/contrib/comments/signals.txt b/docs/ref/contrib/comments/signals.txt index 9d7c435927..8274539ed7 100644 --- a/docs/ref/contrib/comments/signals.txt +++ b/docs/ref/contrib/comments/signals.txt @@ -20,8 +20,8 @@ Sent just before a comment will be saved, after it's been sanity checked and submitted. This can be used to modify the comment (in place) with posting details or other such actions. -If any receiver returns ``False`` the comment will be discarded and a 403 (not -allowed) response will be returned. +If any receiver returns ``False`` the comment will be discarded and a 400 +response will be returned. This signal is sent at more or less the same time (just before, actually) as the ``Comment`` object's :data:`~django.db.models.signals.pre_save` signal. diff --git a/docs/ref/contrib/comments/upgrade.txt b/docs/ref/contrib/comments/upgrade.txt deleted file mode 100644 index dadb53f5fa..0000000000 --- a/docs/ref/contrib/comments/upgrade.txt +++ /dev/null @@ -1,78 +0,0 @@ -=============================================== -Upgrading from Django's previous comment system -=============================================== - -Prior versions of Django included an outdated, undocumented comment system. Users who reverse-engineered this framework will need to upgrade to use the -new comment system; this guide explains how. - -The main changes from the old system are: - -* This new system is documented. - -* It uses modern Django features like :doc:`forms </topics/forms/index>` and - :doc:`modelforms </topics/forms/modelforms>`. - -* It has a single ``Comment`` model instead of separate ``FreeComment`` and - ``Comment`` models. - -* Comments have "email" and "URL" fields. - -* No ratings, photos and karma. This should only effect World Online. - -* The ``{% comment_form %}`` tag no longer exists. Instead, there's now two - functions: ``{% get_comment_form %}``, which returns a form for posting a - new comment, and ``{% render_comment_form %}``, which renders said form - using the ``comments/form.html`` template. - -* The way comments are include in your URLconf have changed; you'll need to - replace:: - - (r'^comments/', include('django.contrib.comments.urls.comments')), - - with:: - - (r'^comments/', include('django.contrib.comments.urls')), - -Upgrading data --------------- - -The data models for Django's comment system have changed, as have the -table names. Before you transfer your existing data into the new comments -system, make sure that you have installed the new comments system as -explained in the -:doc:`quick start guide </ref/contrib/comments/index>`. -This will ensure that the new tables have been properly created. - -To transfer your data into the new comments system, you'll need to directly -run the following SQL: - -.. code-block:: sql - - BEGIN; - - INSERT INTO django_comments - (content_type_id, object_pk, site_id, user_name, user_email, user_url, - comment, submit_date, ip_address, is_public, is_removed) - SELECT - content_type_id, object_id, site_id, person_name, '', '', comment, - submit_date, ip_address, is_public, not approved - FROM comments_freecomment; - - INSERT INTO django_comments - (content_type_id, object_pk, site_id, user_id, user_name, user_email, - user_url, comment, submit_date, ip_address, is_public, is_removed) - SELECT - content_type_id, object_id, site_id, user_id, '', '', '', comment, - submit_date, ip_address, is_public, is_removed - FROM comments_comment; - - UPDATE django_comments SET user_name = ( - SELECT username FROM auth_user - WHERE django_comments.user_id = auth_user.id - ) WHERE django_comments.user_id is not NULL; - UPDATE django_comments SET user_email = ( - SELECT email FROM auth_user - WHERE django_comments.user_id = auth_user.id - ) WHERE django_comments.user_id is not NULL; - - COMMIT; diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 0ced1bf155..3edc019d05 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -493,6 +493,21 @@ Advanced ``WizardView`` methods context = self.get_context_data(form=form, **kwargs) return self.render_to_response(context) +.. method:: WizardView.get_cleaned_data_for_step(step) + + This method returns the cleaned data for a given ``step``. Before returning + the cleaned data, the stored values are revalidated through the form. If + the data doesn't validate, ``None`` will be returned. + +.. method:: WizardView.get_all_cleaned_data() + + This method returns a merged dictionary of all form steps' ``cleaned_data`` + dictionaries. If a step contains a ``FormSet``, the key will be prefixed + with ``formset-`` and contain a list of the formset's ``cleaned_data`` + dictionaries. Note that if two or more steps have a field with the same + name, the value for that field from the latest step will overwrite the + value from any earlier steps. + Providing initial data for the forms ==================================== @@ -534,6 +549,16 @@ This storage will temporarily store the uploaded files for the wizard. The :attr:`file_storage` attribute should be a :class:`~django.core.files.storage.Storage` subclass. +Django provides a built-in storage class (see :ref:`the built-in filesystem +storage class <builtin-fs-storage>`):: + + from django.conf import settings + from django.core.files.storage import FileSystemStorage + + class CustomWizardView(WizardView): + ... + file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos')) + .. warning:: Please remember to take care of removing old files as the @@ -622,8 +647,11 @@ Usage of ``NamedUrlWizardView`` .. class:: NamedUrlWizardView -There is a :class:`WizardView` subclass which adds named-urls support to the wizard. -By doing this, you can have single urls for every step. +There is a :class:`WizardView` subclass which adds named-urls support to the +wizard. By doing this, you can have single urls for every step. You can also +use the :class:`NamedUrlSessionWizardView` or :class:`NamedUrlCookieWizardView` +classes which preselect the backend used for storing information (server-side +sessions and browser cookies respectively). To use the named urls, you have to change the ``urls.py``. diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index eb20b1f411..7d7c32781c 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -656,6 +656,17 @@ is returned instead. Returns the number of interior rings in this geometry. +.. admonition:: Comparing Polygons + + Note that it is possible to compare ``Polygon`` objects directly with ``<`` + or ``>``, but as the comparison is made through Polygon's + :class:`LineString`, it does not mean much (but is consistent and quick). + You can always force the comparison with the :attr:`~GEOSGeometry.area` + property:: + + >>> if poly_1.area > poly_2.area: + >>> pass + Geometry Collections ==================== diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index c710866813..100dc2edd0 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -118,7 +118,7 @@ Invoke the Django shell from your project and execute the .. code-block:: pycon - $ python manage shell + $ python manage.py shell >>> from django.contrib.gis.utils import add_srs_entry >>> add_srs_entry(900913) diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index ec265342b3..5000622ad4 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -5,28 +5,28 @@ GeoDjango Tutorial Introduction ============ -GeoDjango is an add-on for Django that turns it into a world-class geographic -Web framework. GeoDjango strives to make it as simple as possible to create -geographic Web applications, like location-based services. Some features -include: +GeoDjango is an included contrib module for Django that turns it into a +world-class geographic Web framework. GeoDjango strives to make it as simple +as possible to create geographic Web applications, like location-based services. +Its features include: * Django model fields for `OGC`_ geometries. -* Extensions to Django's ORM for the querying and manipulation of spatial data. +* Extensions to Django's ORM for querying and manipulating spatial data. * Loosely-coupled, high-level Python interfaces for GIS geometry operations and data formats. -* Editing of geometry fields inside the admin. +* Editing geometry fields from the admin. -This tutorial assumes a familiarity with Django; thus, if you're brand new to -Django please read through the :doc:`regular tutorial </intro/tutorial01>` to -introduce yourself with basic Django concepts. +This tutorial assumes familiarity with Django; thus, if you're brand new to +Django, please read through the :doc:`regular tutorial </intro/tutorial01>` to +familiarize yourself with Django first. .. note:: - GeoDjango has special prerequisites overwhat is required by Django -- + GeoDjango has additional requirements beyond what Django requires -- please consult the :ref:`installation documentation <ref-gis-install>` for more details. -This tutorial will guide you through the creation of a geographic Web +This tutorial will guide you through the creation of a geographic web application for viewing the `world borders`_. [#]_ Some of the code used in this tutorial is taken from and/or inspired by the `GeoDjango basic apps`_ project. [#]_ @@ -51,10 +51,10 @@ Create a Spatial Database MySQL and Oracle users can skip this section because spatial types are already built into the database. -First, a spatial database needs to be created for our project. If using -PostgreSQL and PostGIS, then the following commands will -create the database from a :ref:`spatial database template -<spatialdb_template>`: +First, create a spatial database for your project. + +If you are using PostGIS, create the database from the :ref:`spatial database +template <spatialdb_template>`: .. code-block:: bash @@ -62,9 +62,9 @@ create the database from a :ref:`spatial database template .. note:: - This command must be issued by a database user that has permissions to - create a database. Here is an example set of commands to create such - a user: + This command must be issued by a database user with enough privileges to + create a database. To create a user with ``CREATE DATABASE`` privileges in + PostgreSQL, use the following commands: .. code-block:: bash @@ -72,25 +72,24 @@ create the database from a :ref:`spatial database template $ createuser --createdb geo $ exit - Replace ``geo`` with the system login user name that will be - connecting to the database. For example, ``johndoe`` if that is the - system user that will be running GeoDjango. + Replace ``geo`` with your Postgres database user's username. + (In PostgreSQL, this user will also be an OS-level user.) -Users of SQLite and SpatiaLite should consult the instructions on how +If you are using SQLite and SpatiaLite, consult the instructions on how to create a :ref:`SpatiaLite database <create_spatialite_db>`. -Create GeoDjango Project +Create a New Project ------------------------ -Use the ``django-admin.py`` script like normal to create a ``geodjango`` -project: +Use the standard ``django-admin.py`` script to create a project called +``geodjango``: .. code-block:: bash $ django-admin.py startproject geodjango -With the project initialized, now create a ``world`` Django application within -the ``geodjango`` project: +This will initialize a new project. Now, create a ``world`` Django application +within the ``geodjango`` project: .. code-block:: bash @@ -101,7 +100,7 @@ Configure ``settings.py`` ------------------------- The ``geodjango`` project settings are stored in the ``geodjango/settings.py`` -file. Edit the database connection settings appropriately:: +file. Edit the database connection settings to match your setup:: DATABASES = { 'default': { @@ -113,7 +112,7 @@ file. Edit the database connection settings appropriately:: In addition, modify the :setting:`INSTALLED_APPS` setting to include :mod:`django.contrib.admin`, :mod:`django.contrib.gis`, -and ``world`` (our newly created application):: +and ``world`` (your newly created application):: INSTALLED_APPS = ( 'django.contrib.auth', @@ -135,9 +134,9 @@ Geographic Data World Borders ------------- -The world borders data is available in this `zip file`__. Create a data +The world borders data is available in this `zip file`__. Create a ``data`` directory in the ``world`` application, download the world borders data, and -unzip. On GNU/Linux platforms the following commands should do it: +unzip. On GNU/Linux platforms, use the following commands: .. code-block:: bash @@ -149,7 +148,7 @@ unzip. On GNU/Linux platforms the following commands should do it: The world borders ZIP file contains a set of data files collectively known as an `ESRI Shapefile`__, one of the most popular geospatial data formats. When -unzipped the world borders data set includes files with the following +unzipped, the world borders dataset includes files with the following extensions: * ``.shp``: Holds the vector data for the world borders geometries. @@ -165,8 +164,8 @@ __ http://en.wikipedia.org/wiki/Shapefile Use ``ogrinfo`` to examine spatial data --------------------------------------- -The GDAL ``ogrinfo`` utility is excellent for examining metadata about -shapefiles (or other vector data sources): +The GDAL ``ogrinfo`` utility allows examining the metadata of shapefiles or +other vector data sources: .. code-block:: bash @@ -175,9 +174,9 @@ shapefiles (or other vector data sources): using driver `ESRI Shapefile' successful. 1: TM_WORLD_BORDERS-0.3 (Polygon) -Here ``ogrinfo`` is telling us that the shapefile has one layer, and that such -layer contains polygon data. To find out more we'll specify the layer name -and use the ``-so`` option to get only important summary information: +``ogrinfo`` tells us that the shapefile has one layer, and that this +layer contains polygon data. To find out more, we'll specify the layer name +and use the ``-so`` option to get only the important summary information: .. code-block:: bash @@ -208,14 +207,11 @@ and use the ``-so`` option to get only important summary information: LAT: Real (7.3) This detailed summary information tells us the number of features in the layer -(246), the geographical extent, the spatial reference system ("SRS WKT"), -as well as detailed information for each attribute field. For example, -``FIPS: String (2.0)`` indicates that there's a ``FIPS`` character field -with a maximum length of 2; similarly, ``LON: Real (8.3)`` is a floating-point -field that holds a maximum of 8 digits up to three decimal places. Although -this information may be found right on the `world borders`_ Web site, this -shows you how to determine this information yourself when such metadata is not -provided. +(246), the geographic bounds of the data, the spatial reference system +("SRS WKT"), as well as type information for each attribute field. For example, +``FIPS: String (2.0)`` indicates that the ``FIPS`` character field has +a maximum length of 2. Similarly, ``LON: Real (8.3)`` is a floating-point +field that holds a maximum of 8 digits up to three decimal places. Geographic Models ================= @@ -223,8 +219,8 @@ Geographic Models Defining a Geographic Model --------------------------- -Now that we've examined our world borders data set using ``ogrinfo``, we can -create a GeoDjango model to represent this data:: +Now that you've examined your dataset using ``ogrinfo``, create a GeoDjango +model to represent this data:: from django.contrib.gis.db import models @@ -252,32 +248,30 @@ create a GeoDjango model to represent this data:: def __unicode__(self): return self.name -Two important things to note: +Please note two important things: 1. The ``models`` module is imported from :mod:`django.contrib.gis.db`. -2. The model overrides its default manager with - :class:`~django.contrib.gis.db.models.GeoManager`; this is *required* - to perform spatial queries. +2. You must override the model's default manager with + :class:`~django.contrib.gis.db.models.GeoManager` to perform spatial queries. -When declaring a geometry field on your model the default spatial reference -system is WGS84 (meaning the `SRID`__ is 4326) -- in other words, the field -coordinates are in longitude/latitude pairs in units of degrees. If you want -the coordinate system to be different, then SRID of the geometry field may be -customized by setting the ``srid`` with an integer corresponding to the -coordinate system of your choice. +The default spatial reference system for geometry fields is WGS84 (meaning +the `SRID`__ is 4326) -- in other words, the field coordinates are in +longitude, latitude pairs in units of degrees. To use a different +coordinate system, set the SRID of the geometry field with the ``srid`` +argument. Use an integer representing the coordinate system's EPSG code. __ http://en.wikipedia.org/wiki/SRID Run ``syncdb`` -------------- -After you've defined your model, it needs to be synced with the spatial -database. First, let's look at the SQL that will generate the table for the +After defining your model, you need to sync it with the database. First, +let's look at the SQL that will generate the table for the ``WorldBorder`` model:: $ python manage.py sqlall world -This management command should produce the following output: +This command should produce the following output: .. code-block:: sql @@ -302,32 +296,28 @@ This management command should produce the following output: CREATE INDEX "world_worldborder_mpoly_id" ON "world_worldborder" USING GIST ( "mpoly" GIST_GEOMETRY_OPS ); COMMIT; -If satisfied, you may then create this table in the database by running the -``syncdb`` management command:: +If this looks correct, run ``syncdb`` to create this table in the database:: $ python manage.py syncdb Creating table world_worldborder Installing custom SQL for world.WorldBorder model -The ``syncdb`` command may also prompt you to create an admin user; go ahead -and do so (not required now, may be done at any point in the future using the -``createsuperuser`` management command). +The ``syncdb`` command may also prompt you to create an admin user. Either +do so now, or later by running ``django-admin.py createsuperuser``. Importing Spatial Data ====================== -This section will show you how to take the data from the world borders -shapefile and import it into GeoDjango models using the +This section will show you how to import the world borders +shapefile into the database via GeoDjango models using the :ref:`ref-layermapping`. -There are many different ways to import data in to a spatial database -- -besides the tools included within GeoDjango, you may also use the following to -populate your spatial database: +There are many different ways to import data into a spatial database -- +besides the tools included within GeoDjango, you may also use the following: -* `ogr2ogr`_: Command-line utility, included with GDAL, that - supports loading a multitude of vector data formats into - the PostGIS, MySQL, and Oracle spatial databases. -* `shp2pgsql`_: This utility is included with PostGIS and only supports - ESRI shapefiles. +* `ogr2ogr`_: A command-line utility included with GDAL that + can import many vector data formats into PostGIS, MySQL, and Oracle databases. +* `shp2pgsql`_: This utility included with PostGIS imports ESRI shapefiles into + PostGIS. .. _ogr2ogr: http://www.gdal.org/ogr2ogr.html .. _shp2pgsql: http://postgis.refractions.net/documentation/manual-1.5/ch04.html#shp2pgsql_usage @@ -337,10 +327,9 @@ populate your spatial database: GDAL Interface -------------- -Earlier we used the ``ogrinfo`` to explore the contents of the world borders -shapefile. Included within GeoDjango is an interface to GDAL's powerful OGR -library -- in other words, you'll be able explore all the vector data sources -that OGR supports via a Pythonic API. +Earlier, you used ``ogrinfo`` to examine the contents of the world borders +shapefile. GeoDjango also includes a Pythonic interface to GDAL's powerful OGR +library that can work with all the vector data sources that OGR supports. First, invoke the Django shell: @@ -348,8 +337,8 @@ First, invoke the Django shell: $ python manage.py shell -If the :ref:`worldborders` data was downloaded like earlier in the -tutorial, then we can determine the path using Python's built-in +If you downloaded the :ref:`worldborders` data earlier in the +tutorial, then you can determine its path using Python's built-in ``os`` module:: >>> import os @@ -357,7 +346,7 @@ tutorial, then we can determine the path using Python's built-in >>> world_shp = os.path.abspath(os.path.join(os.path.dirname(world.__file__), ... 'data/TM_WORLD_BORDERS-0.3.shp')) -Now, the world borders shapefile may be opened using GeoDjango's +Now, open the world borders shapefile using GeoDjango's :class:`~django.contrib.gis.gdal.DataSource` interface:: >>> from django.contrib.gis.gdal import DataSource @@ -374,8 +363,7 @@ shapefiles are only allowed to have one layer:: >>> print(lyr) TM_WORLD_BORDERS-0.3 -You can see what the geometry type of the layer is and how many features it -contains:: +You can see the layer's geometry type and how many features it contains:: >>> print(lyr.geom_type) Polygon @@ -384,16 +372,16 @@ contains:: .. note:: - Unfortunately the shapefile data format does not allow for greater + Unfortunately, the shapefile data format does not allow for greater specificity with regards to geometry types. This shapefile, like - many others, actually includes ``MultiPolygon`` geometries in its - features. You need to watch out for this when creating your models - as a GeoDjango ``PolygonField`` will not accept a ``MultiPolygon`` - type geometry -- thus a ``MultiPolygonField`` is used in our model's - definition instead. + many others, actually includes ``MultiPolygon`` geometries, not Polygons. + It's important to use a more general field type in models: a + GeoDjango ``MultiPolygonField`` will accept a ``Polygon`` geometry, but a + ``PolygonField`` will not accept a ``MultiPolygon`` type geometry. This + is why the ``WorldBorder`` model defined above uses a ``MultiPolygonField``. The :class:`~django.contrib.gis.gdal.Layer` may also have a spatial reference -system associated with it -- if it does, the ``srs`` attribute will return a +system associated with it. If it does, the ``srs`` attribute will return a :class:`~django.contrib.gis.gdal.SpatialReference` object:: >>> srs = lyr.srs @@ -406,9 +394,9 @@ system associated with it -- if it does, the ``srs`` attribute will return a >>> srs.proj4 # PROJ.4 representation '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ' -Here we've noticed that the shapefile is in the popular WGS84 spatial reference -system -- in other words, the data uses units of degrees longitude and -latitude. +This shapefile is in the popular WGS84 spatial reference +system -- in other words, the data uses longitude, latitude pairs in +units of degrees. In addition, shapefiles also support attribute fields that may contain additional data. Here are the fields on the World Borders layer: @@ -416,8 +404,8 @@ additional data. Here are the fields on the World Borders layer: >>> print(lyr.fields) ['FIPS', 'ISO2', 'ISO3', 'UN', 'NAME', 'AREA', 'POP2005', 'REGION', 'SUBREGION', 'LON', 'LAT'] -Here we are examining the OGR types (e.g., whether a field is an integer or -a string) associated with each of the fields: +The following code will let you examine the OGR types (e.g. integer or +string) associated with each of the fields: >>> [fld.__name__ for fld in lyr.field_types] ['OFTString', 'OFTString', 'OFTString', 'OFTInteger', 'OFTString', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTReal', 'OFTReal'] @@ -446,8 +434,7 @@ And individual features may be retrieved by their feature ID:: >>> print(feat.get('NAME')) San Marino -Here the boundary geometry for San Marino is extracted and looking -exported to WKT and GeoJSON:: +Boundary geometries may be exported as WKT and GeoJSON:: >>> geom = feat.geom >>> print(geom.wkt) @@ -459,8 +446,9 @@ exported to WKT and GeoJSON:: ``LayerMapping`` ---------------- -We're going to dive right in -- create a file called ``load.py`` inside the -``world`` application, and insert the following:: +To import the data, use a LayerMapping in a Python script. +Create a file called ``load.py`` inside the ``world`` application, +with the following code:: import os from django.contrib.gis.utils import LayerMapping @@ -492,20 +480,20 @@ We're going to dive right in -- create a file called ``load.py`` inside the A few notes about what's going on: * Each key in the ``world_mapping`` dictionary corresponds to a field in the - ``WorldBorder`` model, and the value is the name of the shapefile field + ``WorldBorder`` model. The value is the name of the shapefile field that data will be loaded from. * The key ``mpoly`` for the geometry field is ``MULTIPOLYGON``, the - geometry type we wish to import as. Even if simple polygons are encountered - in the shapefile they will automatically be converted into collections prior - to insertion into the database. + geometry type GeoDjango will import the field as. Even simple polygons in + the shapefile will automatically be converted into collections prior to + insertion into the database. * The path to the shapefile is not absolute -- in other words, if you move the ``world`` application (with ``data`` subdirectory) to a different location, - then the script will still work. + the script will still work. * The ``transform`` keyword is set to ``False`` because the data in the shapefile does not need to be converted -- it's already in WGS84 (SRID=4326). -* The ``encoding`` keyword is set to the character encoding of string values in - the shapefile. This ensures that string values are read and saved correctly - from their original encoding system. +* The ``encoding`` keyword is set to the character encoding of the string + values in the shapefile. This ensures that string values are read and saved + correctly from their original encoding system. Afterwards, invoke the Django shell from the ``geodjango`` project directory: @@ -513,8 +501,8 @@ Afterwards, invoke the Django shell from the ``geodjango`` project directory: $ python manage.py shell -Next, import the ``load`` module, call the ``run`` routine, and watch ``LayerMapping`` -do the work:: +Next, import the ``load`` module, call the ``run`` routine, and watch +``LayerMapping`` do the work:: >>> from world import load >>> load.run() @@ -536,7 +524,7 @@ The general usage of the command goes as follows: $ python manage.py ogrinspect [options] <data_source> <model_name> [options] -Where ``data_source`` is the path to the GDAL-supported data source and +``data_source`` is the path to the GDAL-supported data source and ``model_name`` is the name to use for the model. Command-line options may be used to further define how the model is generated. @@ -600,9 +588,9 @@ Spatial Queries Spatial Lookups --------------- -GeoDjango extends the Django ORM and allows the use of spatial lookups. -Let's do an example where we find the ``WorldBorder`` model that contains -a point. First, fire up the management shell: +GeoDjango adds spatial lookups to the Django ORM. For example, you +can find the country in the ``WorldBorder`` table that contains +a particular point. First, fire up the management shell: .. code-block:: bash @@ -613,8 +601,8 @@ Now, define a point of interest [#]_:: >>> pnt_wkt = 'POINT(-95.3385 29.7245)' The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude, -and 29.7245 degrees latitude. The geometry is in a format known as -Well Known Text (WKT), an open standard issued by the Open Geospatial +29.7245 degrees latitude. The geometry is in a format known as +Well Known Text (WKT), a standard issued by the Open Geospatial Consortium (OGC). [#]_ Import the ``WorldBorder`` model, and perform a ``contains`` lookup using the ``pnt_wkt`` as the parameter:: @@ -623,11 +611,13 @@ a ``contains`` lookup using the ``pnt_wkt`` as the parameter:: >>> qs [<WorldBorder: United States>] -Here we retrieved a ``GeoQuerySet`` that has only one model: the one -for the United States (which is what we would expect). Similarly, -a :ref:`GEOS geometry object <ref-geos>` may also be used -- here the -``intersects`` spatial lookup is combined with the ``get`` method to retrieve -only the ``WorldBorder`` instance for San Marino instead of a queryset:: +Here, you retrieved a ``GeoQuerySet`` with only one model: the border of +the United States (exactly what you would expect). + +Similarly, you may also use a :ref:`GEOS geometry object <ref-geos>`. +Here, you can combine the ``intersects`` spatial lookup with the ``get`` +method to retrieve only the ``WorldBorder`` instance for San Marino instead +of a queryset:: >>> from django.contrib.gis.geos import Point >>> pnt = Point(12.4604, 43.9420) @@ -635,16 +625,16 @@ only the ``WorldBorder`` instance for San Marino instead of a queryset:: >>> sm <WorldBorder: San Marino> -The ``contains`` and ``intersects`` lookups are just a subset of what's -available -- the :ref:`ref-gis-db-api` documentation has more. +The ``contains`` and ``intersects`` lookups are just a subset of the +available queries -- the :ref:`ref-gis-db-api` documentation has more. Automatic Spatial Transformations --------------------------------- -When querying the spatial database GeoDjango automatically transforms +When doing spatial queries, GeoDjango automatically transforms geometries if they're in a different coordinate system. In the following -example, the coordinate will be expressed in terms of `EPSG SRID 32140`__, +example, coordinates will be expressed in `EPSG SRID 32140`__, a coordinate system specific to south Texas **only** and in units of -**meters** and not degrees:: +**meters**, not degrees:: >>> from django.contrib.gis.geos import Point, GEOSGeometry >>> pnt = Point(954158.1, 4215137.1, srid=32140) @@ -654,7 +644,7 @@ WKT that includes the SRID:: >>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)') -When using GeoDjango's ORM, it will automatically wrap geometry values +GeoDjango's ORM will automatically wrap geometry values in transformation SQL, allowing the developer to work at a higher level of abstraction:: @@ -675,7 +665,7 @@ __ http://spatialreference.org/ref/epsg/32140/ When using :doc:`raw queries </topics/db/sql>`, you should generally wrap your geometry fields with the ``asText()`` SQL function (or ``ST_AsText`` - for PostGIS) so as the field value will be recognized by GEOS:: + for PostGIS) so that the field value will be recognized by GEOS:: City.objects.raw('SELECT id, name, asText(point) from myapp_city') @@ -684,8 +674,8 @@ __ http://spatialreference.org/ref/epsg/32140/ Lazy Geometries --------------- -Geometries come to GeoDjango in a standardized textual representation. Upon -access of the geometry field, GeoDjango creates a `GEOS geometry object +GeoDjango loads geometries in a standardized textual representation. When the +geometry field is first accessed, GeoDjango creates a `GEOS geometry object <ref-geos>`, exposing powerful functionality, such as serialization properties for popular geospatial formats:: @@ -715,14 +705,11 @@ the GEOS library:: Putting your data on the map ============================ -Google ------- - Geographic Admin ---------------- GeoDjango extends :doc:`Django's admin application </ref/contrib/admin/index>` -to enable support for editing geometry fields. +with support for editing geometry fields. Basics ^^^^^^ @@ -730,16 +717,15 @@ Basics GeoDjango also supplements the Django admin by allowing users to create and modify geometries on a JavaScript slippy map (powered by `OpenLayers`_). -Let's dive in again -- create a file called ``admin.py`` inside the -``world`` application, and insert the following:: +Let's dive right in. Create a file called ``admin.py`` inside the +``world`` application with the following code:: from django.contrib.gis import admin from models import WorldBorder admin.site.register(WorldBorder, admin.GeoModelAdmin) -Next, edit your ``urls.py`` in the ``geodjango`` application folder to look -as follows:: +Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows:: from django.conf.urls import patterns, url, include from django.contrib.gis import admin @@ -775,9 +761,9 @@ With the :class:`~django.contrib.gis.admin.OSMGeoAdmin`, GeoDjango uses a `Open Street Map`_ layer in the admin. This provides more context (including street and thoroughfare details) than available with the :class:`~django.contrib.gis.admin.GeoModelAdmin` -(which uses the `Vector Map Level 0`_ WMS data set hosted at `OSGeo`_). +(which uses the `Vector Map Level 0`_ WMS dataset hosted at `OSGeo`_). -First, there are some important requirements and limitations: +First, there are some important requirements: * :class:`~django.contrib.gis.admin.OSMGeoAdmin` requires that the :ref:`spherical mercator projection be added <addgoogleprojection>` @@ -785,14 +771,19 @@ First, there are some important requirements and limitations: * The PROJ.4 datum shifting files must be installed (see the :ref:`PROJ.4 installation instructions <proj4>` for more details). -If you meet these requirements, then just substitute in the ``OSMGeoAdmin`` +If you meet these requirements, then just substitute the ``OSMGeoAdmin`` option class in your ``admin.py`` file:: admin.site.register(WorldBorder, admin.OSMGeoAdmin) .. rubric:: Footnotes -.. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org <http://thematicmapping.org>`_ for providing and maintaining this data set. -.. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and Christopher Schmidt. -.. [#] Here the point is for the `University of Houston Law Center <http://www.law.uh.edu/>`_. -.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL <http://www.opengeospatial.org/standards/sfs>`_. +.. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org + <http://thematicmapping.org>`_ for providing and maintaining this + dataset. +.. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and + Christopher Schmidt. +.. [#] This point is the `University of Houston Law Center + <http://www.law.uh.edu/>`_. +.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification + For SQL <http://www.opengeospatial.org/standards/sfs>`_. diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index bc921a9d33..4fa733edb5 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -341,7 +341,7 @@ This sets the minimum message that will be saved in the message storage. See MESSAGE_STORAGE --------------- -Default: ``'django.contrib.messages.storage.user_messages.FallbackStorage'`` +Default: ``'django.contrib.messages.storage.fallback.FallbackStorage'`` Controls where Django stores message data. Valid values are: diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 790e003453..7e5448b3d3 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -127,8 +127,10 @@ For example:: def my_view(request): if settings.SITE_ID == 3: # Do something. + pass else: # Do something else. + pass Of course, it's ugly to hard-code the site IDs like that. This sort of hard-coding is best for hackish fixes that you need done quickly. The @@ -141,11 +143,13 @@ domain:: current_site = get_current_site(request) if current_site.domain == 'foo.com': # Do something + pass else: # Do something else. + pass -This has also the advantage of checking if the sites framework is installed, and -return a :class:`RequestSite` instance if it is not. +This has also the advantage of checking if the sites framework is installed, +and return a :class:`RequestSite` instance if it is not. If you don't have access to the request object, you can use the ``get_current()`` method of the :class:`~django.contrib.sites.models.Site` @@ -158,8 +162,10 @@ the :setting:`SITE_ID` setting. This example is equivalent to the previous one:: current_site = Site.objects.get_current() if current_site.domain == 'foo.com': # Do something + pass else: # Do something else. + pass Getting the current domain for display -------------------------------------- @@ -200,8 +206,8 @@ subscribing to LJWorld.com alerts." Same goes for the email's message body. Note that an even more flexible (but more heavyweight) way of doing this would be to use Django's template system. Assuming Lawrence.com and LJWorld.com have -different template directories (:setting:`TEMPLATE_DIRS`), you could simply farm out -to the template system like so:: +different template directories (:setting:`TEMPLATE_DIRS`), you could simply +farm out to the template system like so:: from django.core.mail import send_mail from django.template import loader, Context @@ -216,9 +222,9 @@ to the template system like so:: # ... -In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` template -files for both the LJWorld.com and Lawrence.com template directories. That -gives you more flexibility, but it's also more complex. +In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` +template files for both the LJWorld.com and Lawrence.com template directories. +That gives you more flexibility, but it's also more complex. It's a good idea to exploit the :class:`~django.contrib.sites.models.Site` objects as much as possible, to remove unneeded complexity and redundancy. @@ -240,6 +246,15 @@ To do this, you can use the sites framework. A simple example:: >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url()) 'http://example.com/mymodel/objects/3/' + +Default site and ``syncdb`` +=========================== + +``django.contrib.sites`` registers a +:data:`~django.db.models.signals.post_syncdb` signal handler which creates a +default site named ``example.com`` with the domain ``example.com``. For +example, this site will be created after Django creates the test database. + Caching the current ``Site`` object =================================== diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 27b8fc0875..2418dba8ef 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -53,6 +53,7 @@ This simple example, taken from `chicagocrime.org`_, describes a feed of the latest five news items:: from django.contrib.syndication.views import Feed + from django.core.urlresolvers import reverse from chicagocrime.models import NewsItem class LatestEntriesFeed(Feed): @@ -69,6 +70,10 @@ latest five news items:: def item_description(self, item): return item.description + # item_link is only needed if NewsItem has no get_absolute_url method. + def item_link(self, item): + return reverse('news-item', args=[item.pk]) + To connect a URL to this feed, put an instance of the Feed object in your :doc:`URLconf </topics/http/urls>`. For example:: diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 3a52f838e7..352c0f4584 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -33,6 +33,11 @@ aggregate with a database backend that falls within the affected release range. .. _known to be faulty: http://archives.postgresql.org/pgsql-bugs/2007-07/msg00046.php .. _Release 8.2.5: http://www.postgresql.org/docs/devel/static/release-8-2-5.html +PostgreSQL connection settings +------------------------------- + +See :setting:`HOST` for details. + Optimizing PostgreSQL's configuration ------------------------------------- diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7fa7539985..306db8439e 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -96,6 +96,9 @@ cleanup Can be run as a cronjob or directly to clean out old data from the database (only expired sessions at the moment). +.. versionchanged:: 1.5 + :djadmin:`cleanup` is deprecated. Use :djadmin:`clearsessions` instead. + compilemessages --------------- @@ -876,14 +879,19 @@ either the path to a directory with the app template file, or a path to a compressed file (``.tar.gz``, ``.tar.bz2``, ``.tgz``, ``.tbz``, ``.zip``) containing the app template files. +For example, this would look for an app template in the given directory when +creating the ``myapp`` app:: + + django-admin.py startapp --template=/Users/jezdez/Code/my_app_template myapp + Django will also accept URLs (``http``, ``https``, ``ftp``) to compressed archives with the app template files, downloading and extracting them on the fly. -For example, this would look for an app template in the given directory when -creating the ``myapp`` app:: +For example, taking advantage of Github's feature to expose repositories as +zip files, you can use a URL like:: - django-admin.py startapp --template=/Users/jezdez/Code/my_app_template myapp + django-admin.py startapp --template=https://github.com/githubuser/django-app-template/archive/master.zip myapp .. versionadded:: 1.4 @@ -951,6 +959,15 @@ when creating the ``myproject`` project:: django-admin.py startproject --template=/Users/jezdez/Code/my_project_template myproject +Django will also accept URLs (``http``, ``https``, ``ftp``) to compressed +archives with the project template files, downloading and extracting them on the +fly. + +For example, taking advantage of Github's feature to expose repositories as +zip files, you can use a URL like:: + + django-admin.py startproject --template=https://github.com/githubuser/django-project-template/archive/master.zip myproject + When Django copies the project template files, it also renders certain files through the template engine: the files whose extensions match the ``--extension`` option (``py`` by default) and the files whose names are passed @@ -1187,6 +1204,16 @@ This command is only available if :doc:`GeoDjango </ref/contrib/gis/index>` Please refer to its :djadmin:`description <ogrinspect>` in the GeoDjango documentation. +``django.contrib.sessions`` +--------------------------- + +clearsessions +~~~~~~~~~~~~~~~ + +.. django-admin:: clearsessions + +Can be run as a cron job or directly to clean out expired sessions. + ``django.contrib.sitemaps`` --------------------------- @@ -1456,3 +1483,12 @@ Examples:: from django.core import management management.call_command('flush', verbosity=0, interactive=False) management.call_command('loaddata', 'test_data', verbosity=0) + +Output redirection +================== + +Note that you can redirect standard output and error streams as all commands +support the ``stdout`` and ``stderr`` options. For example, you could write:: + + with open('/tmp/command_output') as f: + management.call_command('dumpdata', stdout=f) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 7c8d509031..75d05c6829 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -683,7 +683,7 @@ For each field, we describe the default widget used if you don't specify .. attribute:: unpack_ipv4 - Unpacks IPv4 mapped addresses like ``::ffff::192.0.2.1``. + Unpacks IPv4 mapped addresses like ``::ffff:192.0.2.1``. If this option is enabled that address would be unpacked to ``192.0.2.1``. Default is disabled. Can only be used when ``protocol`` is set to ``'both'``. diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 3c458930fa..a0ef0731ad 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -214,38 +214,49 @@ foundation for custom widgets. The 'value' given is not guaranteed to be valid input, therefore subclass implementations should program defensively. + .. method:: value_from_datadict(self, data, files, name) + + Given a dictionary of data and this widget's name, returns the value + of this widget. Returns ``None`` if a value wasn't provided. + .. class:: MultiWidget(widgets, attrs=None) A widget that is composed of multiple widgets. :class:`~django.forms.widgets.MultiWidget` works hand in hand with the :class:`~django.forms.MultiValueField`. - .. method:: render(name, value, attrs=None) + :class:`MultiWidget` has one required argument: - Argument `value` is handled differently in this method from the - subclasses of :class:`~Widget`. + .. attribute:: MultiWidget.widgets - If `value` is a list, output of :meth:`~MultiWidget.render` will be a - concatenation of rendered child widgets. If `value` is not a list, it - will be first processed by the method :meth:`~MultiWidget.decompress()` - to create the list and then processed as above. + An iterable containing the widgets needed. - Unlike in the single value widgets, method :meth:`~MultiWidget.render` - need not be implemented in the subclasses. + And one required method: .. method:: decompress(value) - Returns a list of "decompressed" values for the given value of the - multi-value field that makes use of the widget. The input value can be - assumed as valid, but not necessarily non-empty. + This method takes a single "compressed" value from the field and + returns a list of "decompressed" values. The input value can be + assumed valid, but not necessarily non-empty. This method **must be implemented** by the subclass, and since the value may be empty, the implementation must be defensive. The rationale behind "decompression" is that it is necessary to "split" - the combined value of the form field into the values of the individual - field encapsulated within the multi-value field (e.g. when displaying - the partially or fully filled-out form). + the combined value of the form field into the values for each widget. + + An example of this is how :class:`SplitDateTimeWidget` turns a + :class:`datetime` value into a list with date and time split into two + separate values:: + + class SplitDateTimeWidget(MultiWidget): + + # ... + + def decompress(self, value): + if value: + return [value.date(), value.time().replace(microsecond=0)] + return [None, None] .. tip:: @@ -254,6 +265,109 @@ foundation for custom widgets. with the opposite responsibility - to combine cleaned values of all member fields into one. + Other methods that may be useful to override include: + + .. method:: render(name, value, attrs=None) + + Argument ``value`` is handled differently in this method from the + subclasses of :class:`~Widget` because it has to figure out how to + split a single value for display in multiple widgets. + + The ``value`` argument used when rendering can be one of two things: + + * A ``list``. + * A single value (e.g., a string) that is the "compressed" representation + of a ``list`` of values. + + If `value` is a list, output of :meth:`~MultiWidget.render` will be a + concatenation of rendered child widgets. If `value` is not a list, it + will be first processed by the method :meth:`~MultiWidget.decompress()` + to create the list and then processed as above. + + In the second case -- i.e., if the value is *not* a list -- + ``render()`` will first decompress the value into a ``list`` before + rendering it. It does so by calling the ``decompress()`` method, which + :class:`MultiWidget`'s subclasses must implement (see above). + + When ``render()`` executes its HTML rendering, each value in the list + is rendered with the corresponding widget -- the first value is + rendered in the first widget, the second value is rendered in the + second widget, etc. + + Unlike in the single value widgets, method :meth:`~MultiWidget.render` + need not be implemented in the subclasses. + + .. method:: format_output(rendered_widgets) + + Given a list of rendered widgets (as strings), returns a Unicode string + representing the HTML for the whole lot. + + This hook allows you to format the HTML design of the widgets any way + you'd like. + + Here's an example widget which subclasses :class:`MultiWidget` to display + a date with the day, month, and year in different select boxes. This widget + is intended to be used with a :class:`~django.forms.DateField` rather than + a :class:`~django.forms.MultiValueField`, thus we have implemented + :meth:`~Widget.value_from_datadict`:: + + from datetime import date + from django.forms import widgets + + class DateSelectorWidget(widgets.MultiWidget): + def __init__(self, attrs=None): + # create choices for days, months, years + # example below, the rest snipped for brevity. + years = [(year, year) for year in (2011, 2012, 2013)] + _widgets = ( + widgets.Select(attrs=attrs, choices=days), + widgets.Select(attrs=attrs, choices=months), + widgets.Select(attrs=attrs, choices=years), + ) + super(DateSelectorWidget, self).__init__(_widgets, attrs) + + def decompress(self, value): + if value: + return [value.day, value.month, value.year] + return [None, None, None] + + def format_output(self, rendered_widgets): + return u''.join(rendered_widgets) + + def value_from_datadict(self, data, files, name): + datelist = [ + widget.value_from_datadict(data, files, name + '_%s' % i) + for i, widget in enumerate(self.widgets)] + try: + D = date(day=int(datelist[0]), month=int(datelist[1]), + year=int(datelist[2])) + except ValueError: + return '' + else: + return str(D) + + The constructor creates several :class:`Select` widgets in a tuple. The + ``super`` class uses this tuple to setup the widget. + + The :meth:`~MultiWidget.format_output` method is fairly vanilla here (in + fact, it's the same as what's been implemented as the default for + ``MultiWidget``), but the idea is that you could add custom HTML between + the widgets should you wish. + + The required method :meth:`~MultiWidget.decompress` breaks up a + ``datetime.date`` value into the day, month, and year values corresponding + to each widget. Note how the method handles the case where ``value`` is + ``None``. + + The default implementation of :meth:`~Widget.value_from_datadict` returns + a list of values corresponding to each ``Widget``. This is appropriate + when using a ``MultiWidget`` with a :class:`~django.forms.MultiValueField`, + but since we want to use this widget with a :class:`~django.forms.DateField` + which takes a single value, we have overridden this method to combine the + data of all the subwidgets into a ``datetime.date``. The method extracts + data from the ``POST`` dictionary and constructs and validates the date. + If it is valid, we return the string, otherwise, we return an empty string + which will cause ``form.is_valid`` to return ``False``. .. _built-in widgets: @@ -552,54 +666,6 @@ Composite widgets :attr:`~Field.choices` attribute. If it does, it will override anything you set here when the attribute is updated on the :class:`Field`. -``MultiWidget`` -~~~~~~~~~~~~~~~ - -.. class:: MultiWidget - - Wrapper around multiple other widgets. You'll probably want to use this - class with :class:`MultiValueField`. - - Its ``render()`` method is different than other widgets', because it has to - figure out how to split a single value for display in multiple widgets. - - Subclasses may implement ``format_output``, which takes the list of - rendered widgets and returns a string of HTML that formats them any way - you'd like. - - The ``value`` argument used when rendering can be one of two things: - - * A ``list``. - * A single value (e.g., a string) that is the "compressed" representation - of a ``list`` of values. - - In the second case -- i.e., if the value is *not* a list -- ``render()`` - will first decompress the value into a ``list`` before rendering it. It - does so by calling the ``decompress()`` method, which - :class:`MultiWidget`'s subclasses must implement. This method takes a - single "compressed" value and returns a ``list``. An example of this is how - :class:`SplitDateTimeWidget` turns a :class:`datetime` value into a list - with date and time split into two seperate values:: - - class SplitDateTimeWidget(MultiWidget): - - # ... - - def decompress(self, value): - if value: - return [value.date(), value.time().replace(microsecond=0)] - return [None, None] - - When ``render()`` executes its HTML rendering, each value in the list is - rendered with the corresponding widget -- the first value is rendered in - the first widget, the second value is rendered in the second widget, etc. - - :class:`MultiWidget` has one required argument: - - .. attribute:: MultiWidget.widgets - - An iterable containing the widgets needed. - ``SplitDateTimeWidget`` ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 809d56eaf5..cd1185585c 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -825,7 +825,7 @@ are converted to lowercase. .. attribute:: GenericIPAddressField.unpack_ipv4 - Unpacks IPv4 mapped addresses like ``::ffff::192.0.2.1``. + Unpacks IPv4 mapped addresses like ``::ffff:192.0.2.1``. If this option is enabled that address would be unpacked to ``192.0.2.1``. Default is disabled. Can only be used when ``protocol`` is set to ``'both'``. @@ -922,6 +922,11 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional :attr:`~CharField.max_length`argument. If you don't specify :attr:`~CharField.max_length`, a default of 200 is used. +.. versionadded:: 1.5 + +The current value of the field will be displayed as a clickable link above the +input widget. + Relationship fields =================== diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 1ba41148b0..6315985ba9 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -67,9 +67,9 @@ Validating objects There are three steps involved in validating a model: -1. Validate the model fields -2. Validate the model as a whole -3. Validate the field uniqueness +1. Validate the model fields - :meth:`Model.clean_fields()` +2. Validate the model as a whole - :meth:`Model.clean()` +3. Validate the field uniqueness - :meth:`Model.validate_unique()` All three steps are performed when you call a model's :meth:`~Model.full_clean()` method. @@ -97,17 +97,20 @@ not be corrected by the user. Note that ``full_clean()`` will *not* be called automatically when you call your model's :meth:`~Model.save()` method, nor as a result of -:class:`~django.forms.ModelForm` validation. You'll need to call it manually -when you want to run one-step model validation for your own manually created -models. +:class:`~django.forms.ModelForm` validation. In the case of +:class:`~django.forms.ModelForm` validation, :meth:`Model.clean_fields()`, +:meth:`Model.clean()`, and :meth:`Model.validate_unique()` are all called +individually. -Example:: +You'll need to call ``full_clean`` manually when you want to run one-step model +validation for your own manually created models. For example:: try: article.full_clean() except ValidationError as e: # Do something based on the errors contained in e.message_dict. # Display them to a user, or handle them programatically. + pass The first step ``full_clean()`` performs is to clean each individual field. @@ -375,7 +378,7 @@ If ``save()`` is passed a list of field names in keyword argument ``update_fields``, only the fields named in that list will be updated. This may be desirable if you want to update just one or a few fields on an object. There will be a slight performance benefit from preventing -all of the model fields from being updated in the database. For example: +all of the model fields from being updated in the database. For example:: product.name = 'Name changed again' product.save(update_fields=['name']) @@ -479,9 +482,13 @@ For example:: return "/people/%i/" % self.id (Whilst this code is correct and simple, it may not be the most portable way to -write this kind of method. The :func:`permalink() decorator <permalink>`, -documented below, is usually the best approach and you should read that section -before diving into code implementation.) +write this kind of method. The :func:`~django.core.urlresolvers.reverse` +function is usually the best approach.) + +For example:: + + def get_absolute_url(self): + return reverse('people.views.details', args=[str(self.id)]) One place Django uses ``get_absolute_url()`` is in the admin app. If an object defines this method, the object-editing page will have a "View on site" link @@ -526,11 +533,19 @@ in ``get_absolute_url()`` and have all your other code call that one place. The ``permalink`` decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The way we wrote ``get_absolute_url()`` above is a slightly violation of the -DRY principle: the URL for this object is defined both in the URLconf file and -in the model. +.. warning:: -You can decouple your models from the URLconf using the ``permalink`` decorator: + The ``permalink`` decorator is no longer recommended. You should use + :func:`~django.core.urlresolvers.reverse` in the body of your + ``get_absolute_url`` method instead. + +In early versions of Django, there wasn't an easy way to use URLs defined in +URLconf file inside :meth:`~django.db.models.Model.get_absolute_url`. That +meant you would need to define the URL both in URLConf and +:meth:`~django.db.models.Model.get_absolute_url`. The ``permalink`` decorator +was added to overcome this DRY principle violation. However, since the +introduction of :func:`~django.core.urlresolvers.reverse` there is no +reason to use ``permalink`` any more. .. function:: permalink() @@ -541,14 +556,14 @@ correct URL, with all parameters substituted in the correct positions. The ``permalink`` decorator is a Python-level equivalent to the :ttag:`url` template tag and a high-level wrapper for the -:func:`django.core.urlresolvers.reverse()` function. +:func:`~django.core.urlresolvers.reverse` function. An example should make it clear how to use ``permalink()``. Suppose your URLconf contains a line such as:: (r'^people/(\d+)/$', 'people.views.details'), -...your model could have a :meth:`~django.db.models.Model.get_absolute_url()` +...your model could have a :meth:`~django.db.models.Model.get_absolute_url` method that looked like this:: from django.db import models @@ -616,25 +631,25 @@ the field. This method returns the "human-readable" value of the field. For example:: - from django.db import models + from django.db import models - class Person(models.Model): - SHIRT_SIZES = ( - (u'S', u'Small'), - (u'M', u'Medium'), - (u'L', u'Large'), - ) - name = models.CharField(max_length=60) - shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) + class Person(models.Model): + SHIRT_SIZES = ( + (u'S', u'Small'), + (u'M', u'Medium'), + (u'L', u'Large'), + ) + name = models.CharField(max_length=60) + shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES) - :: +:: - >>> p = Person(name="Fred Flintstone", shirt_size="L") - >>> p.save() - >>> p.shirt_size - u'L' - >>> p.get_shirt_size_display() - u'Large' + >>> p = Person(name="Fred Flintstone", shirt_size="L") + >>> p.save() + >>> p.shirt_size + u'L' + >>> p.get_shirt_size_display() + u'Large' .. method:: Model.get_next_by_FOO(\**kwargs) .. method:: Model.get_previous_by_FOO(\**kwargs) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index c5ae8398ea..a577135271 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -261,6 +261,21 @@ Django quotes column and table names behind the scenes. :class:`~django.db.models.ManyToManyField`, try using a signal or an explicit :attr:`through <ManyToManyField.through>` model. +``index_together`` + +.. attribute:: Options.index_together + + .. versionadded:: 1.5 + + Sets of field names that, taken together, are indexed:: + + index_together = [ + ["pub_date", "deadline"], + ] + + This list of fields will be indexed together (i.e. the appropriate + ``CREATE INDEX`` statement will be issued.) + ``verbose_name`` ---------------- diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 7138cd0e74..40fa2d2b2f 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -676,21 +676,12 @@ Note that, by default, ``select_related()`` does not follow foreign keys that have ``null=True``. Usually, using ``select_related()`` can vastly improve performance because your -app can avoid many database calls. However, in situations with deeply nested -sets of relationships ``select_related()`` can sometimes end up following "too -many" relations, and can generate queries so large that they end up being slow. +app can avoid many database calls. However, there are times you are only +interested in specific related models, or have deeply nested sets of +relationships, and in these cases ``select_related()`` can be optimized by +explicitly passing the related field names you are interested in. Only +the specified relations will be followed. -In these situations, you can use the ``depth`` argument to ``select_related()`` -to control how many "levels" of relations ``select_related()`` will actually -follow:: - - b = Book.objects.select_related(depth=1).get(id=4) - p = b.author # Doesn't hit the database. - c = p.hometown # Requires a database call. - -Sometimes you only want to access specific models that are related to your root -model, not all of the related models. In these cases, you can pass the related -field names to ``select_related()`` and it will only follow those relations. You can even do this for models that are more than one relation away by separating the field names with double underscores, just as for filters. For example, if you have this model:: @@ -730,6 +721,17 @@ You can also refer to the reverse direction of a is defined. Instead of specifying the field name, use the :attr:`related_name <django.db.models.ForeignKey.related_name>` for the field on the related object. +.. deprecated:: 1.5 + The ``depth`` parameter to ``select_related()`` has been deprecated. You + should replace it with the use of the ``(*fields)`` listing specific + related fields instead as documented above. + +A depth limit of relationships to follow can also be specified:: + + b = Book.objects.select_related(depth=1).get(id=4) + p = b.author # Doesn't hit the database. + c = p.hometown # Requires a database call. + A :class:`~django.db.models.OneToOneField` is not traversed in the reverse direction if you are performing a depth-based ``select_related()`` call. @@ -1319,10 +1321,12 @@ The above example can be rewritten using ``get_or_create()`` like so:: Any keyword arguments passed to ``get_or_create()`` — *except* an optional one called ``defaults`` — will be used in a :meth:`get()` call. If an object is -found, ``get_or_create()`` returns a tuple of that object and ``False``. If an -object is *not* found, ``get_or_create()`` will instantiate and save a new -object, returning a tuple of the new object and ``True``. The new object will -be created roughly according to this algorithm:: +found, ``get_or_create()`` returns a tuple of that object and ``False``. If +multiple objects are found, ``get_or_create`` raises +:exc:`~django.core.exceptions.MultipleObjectsReturned`. If an object is *not* +found, ``get_or_create()`` will instantiate and save a new object, returning a +tuple of the new object and ``True``. The new object will be created roughly +according to this algorithm:: defaults = kwargs.pop('defaults', {}) params = dict([(k, v) for k, v in kwargs.items() if '__' not in k]) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index a909c12665..daa4ee9a46 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -431,10 +431,12 @@ MySQL will connect via a Unix socket to the specified socket. For example:: If you're using MySQL and this value *doesn't* start with a forward slash, then this value is assumed to be the host. -If you're using PostgreSQL, an empty string means to use a Unix domain socket -for the connection, rather than a network connection to localhost. If you -explicitly need to use a TCP/IP connection on the local machine with -PostgreSQL, specify ``localhost`` here. +If you're using PostgreSQL, by default (empty :setting:`HOST`), the connection +to the database is done through UNIX domain sockets ('local' lines in +``pg_hba.conf``). If you want to connect through TCP sockets, set +:setting:`HOST` to 'localhost' or '127.0.0.1' ('host' lines in ``pg_hba.conf``). +On Windows, you should always define :setting:`HOST`, as UNIX domain sockets +are not available. .. setting:: NAME @@ -1119,9 +1121,11 @@ Default: ``()`` List of compiled regular expression objects describing URLs that should be ignored when reporting HTTP 404 errors via email (see -:doc:`/howto/error-reporting`). Use this if your site does not provide a -commonly requested file such as ``favicon.ico`` or ``robots.txt``, or if it -gets hammered by script kiddies. +:doc:`/howto/error-reporting`). Regular expressions are matched against +:meth:`request's full paths <django.http.HttpRequest.get_full_path>` (including +query string, if any). Use this if your site does not provide a commonly +requested file such as ``favicon.ico`` or ``robots.txt``, or if it gets +hammered by script kiddies. This is only used if :setting:`SEND_BROKEN_LINK_EMAILS` is set to ``True`` and ``CommonMiddleware`` is installed (see :doc:`/topics/http/middleware`). @@ -1242,9 +1246,8 @@ Example:: '/var/local/translations/locale' ) -Note that in the paths you add to the value of this setting, if you have the -typical ``/path/to/locale/xx/LC_MESSAGES`` hierarchy, you should use the path to -the ``locale`` directory (i.e. ``'/path/to/locale'``). +Django will look within each of these paths for the ``<locale_code>/LC_MESSAGES`` +directories containing the actual translation files. .. setting:: LOGGING @@ -1373,7 +1376,7 @@ more details. MESSAGE_STORAGE --------------- -Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` +Default: ``'django.contrib.messages.storage.fallback.FallbackStorage'`` Controls where Django stores message data. See the :doc:`messages documentation </ref/contrib/messages>` for more details. @@ -1560,9 +1563,9 @@ for. You'll need to set a tuple with two elements -- the name of the header to look for and the required value. For example:: - SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -Here, we're telling Django that we trust the ``X-Forwarded-Protocol`` header +Here, we're telling Django that we trust the ``X-Forwarded-Proto`` header that comes from our proxy, and any time its value is ``'https'``, then the request is guaranteed to be secure (i.e., it originally came in via HTTPS). Obviously, you should *only* set this setting if you control your proxy or @@ -1575,16 +1578,18 @@ available in ``request.META``.) .. warning:: - **You will probably open security holes in your site if you set this without knowing what you're doing. And if you fail to set it when you should. Seriously.** + **You will probably open security holes in your site if you set this + without knowing what you're doing. And if you fail to set it when you + should. Seriously.** Make sure ALL of the following are true before setting this (assuming the values from the example above): * Your Django app is behind a proxy. - * Your proxy strips the 'X-Forwarded-Protocol' header from all incoming + * Your proxy strips the ``X-Forwarded-Proto`` header from all incoming requests. In other words, if end users include that header in their requests, the proxy will discard it. - * Your proxy sets the 'X-Forwarded-Protocol' header and sends it to Django, + * Your proxy sets the ``X-Forwarded-Proto`` header and sends it to Django, but only for requests that originally come in via HTTPS. If any of those are not true, you should keep this setting set to ``None`` @@ -1693,6 +1698,16 @@ This is useful if you have multiple Django instances running under the same hostname. They can use different cookie paths, and each instance will only see its own session cookie. +.. setting:: SESSION_CACHE_ALIAS + +SESSION_CACHE_ALIAS +------------------- + +Default: ``default`` + +If you're using :ref:`cache-based session storage <cached-sessions-backend>`, +this selects the cache to use. + .. setting:: SESSION_COOKIE_SECURE SESSION_COOKIE_SECURE @@ -2054,6 +2069,16 @@ to ensure your processes are running in the correct environment. .. _pytz: http://pytz.sourceforge.net/ +.. setting:: TRANSACTIONS_MANAGED + +TRANSACTIONS_MANAGED +-------------------- + +Default: ``False`` + +Set this to ``True`` if you want to :ref:`disable Django's transaction +management <deactivate-transaction-management>` and implement your own. + .. setting:: USE_ETAGS USE_ETAGS @@ -2198,16 +2223,6 @@ The default value for the X-Frame-Options header used by Deprecated settings =================== -.. setting:: ADMIN_MEDIA_PREFIX - -ADMIN_MEDIA_PREFIX ------------------- - -.. deprecated:: 1.4 - This setting has been obsoleted by the ``django.contrib.staticfiles`` app - integration. See the :doc:`Django 1.4 release notes</releases/1.4>` for - more information. - .. setting:: AUTH_PROFILE_MODULE AUTH_PROFILE_MODULE diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 3b8d058fb4..57ef0cfb27 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -53,6 +53,13 @@ comment Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. +Sample usage:: + + <p>Rendered text with {{ pub_date|date:"c" }}</p> + {% comment %} + <p>Commented out text with {{ create_date|date:"c" }}</p> + {% endcomment %} + .. templatetag:: csrf_token csrf_token @@ -611,7 +618,7 @@ Output the contents of the block if the two arguments equal each other. Example:: - {% ifequal user.id comment.user_id %} + {% ifequal user.pk comment.user_id %} ... {% endifequal %} @@ -947,6 +954,10 @@ Argument Outputs ``closecomment`` ``#}`` ================== ======= +Sample usage:: + + {% templatetag openblock %} url 'entry_list' {% templatetag closeblock %} + .. templatetag:: url url @@ -1024,6 +1035,16 @@ This will follow the normal :ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`, including using any hints provided by the context as to the current application. +.. warning:: + + Don't forget to put quotes around the function path or pattern name! + + .. versionchanged:: 1.5 + The first parameter used not to be quoted, which was inconsistent with + other template tags. Since Django 1.5, it is evaluated according to + the usual rules: it can be a quoted string or a variable that will be + looked up in the context. + .. templatetag:: verbatim verbatim @@ -1226,7 +1247,8 @@ G Hour, 24-hour format without leading ``'0'`` to ``'23'`` h Hour, 12-hour format. ``'01'`` to ``'12'`` H Hour, 24-hour format. ``'00'`` to ``'23'`` i Minutes. ``'00'`` to ``'59'`` -I Not implemented. +I Daylight Savings Time, whether it's ``'1'`` or ``'0'`` + in effect or not. j Day of the month without leading ``'1'`` to ``'31'`` zeros. l Day of the week, textual, long. ``'Friday'`` @@ -1408,6 +1430,12 @@ applied to the result will only result in one round of escaping being done. So it is safe to use this function even in auto-escaping environments. If you want multiple escaping passes to be applied, use the :tfilter:`force_escape` filter. +For example, you can apply ``escape`` to fields when :ttag:`autoescape` is off:: + + {% autoescape off %} + {{ title|escape }} + {% endautoescape %} + .. templatefilter:: escapejs escapejs @@ -1438,6 +1466,14 @@ For example:: If ``value`` is 123456789, the output would be ``117.7 MB``. +.. admonition:: File sizes and SI units + + Strictly speaking, ``filesizeformat`` does not conform to the International + System of Units which recommends using KiB, MiB, GiB, etc. when byte sizes + are calculated in powers of 1024 (which is the case here). Instead, Django + uses traditional unit names (KB, MB, GB, etc.) corresponding to names that + are more commonly used. + .. templatefilter:: first first @@ -1504,6 +1540,17 @@ that many decimal places. For example: ``34.26000`` ``{{ value|floatformat:3 }}`` ``34.260`` ============ ============================= ========== +Particularly useful is passing 0 (zero) as the argument which will round the +float to the nearest integer. + +============ ================================ ========== +``value`` Template Output +============ ================================ ========== +``34.23234`` ``{{ value|floatformat:"0" }}`` ``34`` +``34.00000`` ``{{ value|floatformat:"0" }}`` ``34`` +``39.56000`` ``{{ value|floatformat:"0" }}`` ``40`` +============ ================================ ========== + If the argument passed to ``floatformat`` is negative, it will round a number to that many decimal places -- but only if there's a decimal part to be displayed. For example: @@ -1530,6 +1577,13 @@ string. This is useful in the rare cases where you need multiple escaping or want to apply other filters to the escaped results. Normally, you want to use the :tfilter:`escape` filter. +For example, if you want to catch the ``<p>`` HTML elements created by +the :tfilter:`linebreaks` filter:: + + {% autoescape off %} + {{ body|linebreaks|force_escape }} + {% endautoescape %} + .. templatefilter:: get_digit get_digit @@ -1899,9 +1953,9 @@ for documentation of Python string formatting For example:: - {{ value|stringformat:"s" }} + {{ value|stringformat:"E" }} -If ``value`` is ``"Joel is a slug"``, the output will be ``"Joel is a slug"``. +If ``value`` is ``10``, the output will be ``1.000000E+01``. .. templatefilter:: striptags @@ -1967,7 +2021,9 @@ Takes an optional argument that is a variable containing the date to use as the comparison point (without the argument, the comparison point is *now*). For example, if ``blog_date`` is a date instance representing midnight on 1 June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006, -then ``{{ blog_date|timesince:comment_date }}`` would return "8 hours". +then the following would return "8 hours":: + + {{ blog_date|timesince:comment_date }} Comparing offset-naive and offset-aware datetimes will return an empty string. @@ -1986,7 +2042,9 @@ given date or datetime. For example, if today is 1 June 2006 and Takes an optional argument that is a variable containing the date to use as the comparison point (instead of *now*). If ``from_date`` contains 22 June -2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week". +2006, then the following will return "1 week":: + + {{ conference_date|timeuntil:from_date }} Comparing offset-naive and offset-aware datetimes will return an empty string. diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index ffab647379..784ff33398 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -262,11 +262,11 @@ Taking care in ``get_absolute_url()`` URLs can only contain ASCII characters. If you're constructing a URL from pieces of data that might be non-ASCII, be careful to encode the results in a -way that is suitable for a URL. The ``django.db.models.permalink()`` decorator -handles this for you automatically. +way that is suitable for a URL. The :func:`~django.core.urlresolvers.reverse` +function handles this for you automatically. -If you're constructing a URL manually (i.e., *not* using the ``permalink()`` -decorator), you'll need to take care of the encoding yourself. In this case, +If you're constructing a URL manually (i.e., *not* using the ``reverse()`` +function), you'll need to take care of the encoding yourself. In this case, use the ``iri_to_uri()`` and ``urlquote()`` functions that were documented above_. For example:: diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index 1bb33c7ca1..528f172061 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -178,25 +178,17 @@ whether a view would raise a ``Http404`` error before redirecting to it:: return HttpResponseRedirect('/') return response - -permalink() ------------ - -The :func:`~django.db.models.permalink` decorator is useful for writing short -methods that return a full URL path. For example, a model's -``get_absolute_url()`` method. See :func:`django.db.models.permalink` for more. - get_script_prefix() ------------------- .. function:: get_script_prefix() -Normally, you should always use :func:`~django.core.urlresolvers.reverse` or -:func:`~django.db.models.permalink` to define URLs within your application. -However, if your application constructs part of the URL hierarchy itself, you -may occasionally need to generate URLs. In that case, you need to be able to -find the base URL of the Django project within its Web server -(normally, :func:`~django.core.urlresolvers.reverse` takes care of this for -you). In that case, you can call ``get_script_prefix()``, which will return the -script prefix portion of the URL for your Django project. If your Django -project is at the root of its Web server, this is always ``"/"``. +Normally, you should always use :func:`~django.core.urlresolvers.reverse` to +define URLs within your application. However, if your application constructs +part of the URL hierarchy itself, you may occasionally need to generate URLs. +In that case, you need to be able to find the base URL of the Django project +within its Web server (normally, :func:`~django.core.urlresolvers.reverse` +takes care of this for you). In that case, you can call +``get_script_prefix()``, which will return the script prefix portion of the URL +for your Django project. If your Django project is at the root of its web +server, this is always ``"/"``. diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index bd3898172a..2f12c3a96c 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -305,6 +305,18 @@ The functions defined in this module share the following properties: Returns an ASCII string containing the encoded result. +.. function:: filepath_to_uri(path) + + Convert a file system path to a URI portion that is suitable for inclusion + in a URL. The path is assumed to be either UTF-8 or unicode. + + This method will encode certain characters that would normally be + recognized as special characters for URIs. Note that this method does not + encode the ' character, as it is a valid character within URIs. See + ``encodeURIComponent()`` JavaScript function for more details. + + Returns an ASCII string containing the encoded result. + ``django.utils.feedgenerator`` ============================== diff --git a/docs/releases/1.0-beta-2.txt b/docs/releases/1.0-beta-2.txt index 288ac8fbc1..fac64d8433 100644 --- a/docs/releases/1.0-beta-2.txt +++ b/docs/releases/1.0-beta-2.txt @@ -32,8 +32,8 @@ Refactored ``django.contrib.comments`` carried out a major rewrite and refactoring of Django's bundled comment system, greatly increasing its flexibility and customizability. :doc:`Full documentation - </ref/contrib/comments/index>` is available, as well as :doc:`an - upgrade guide </ref/contrib/comments/upgrade>` if you were using + </ref/contrib/comments/index>` is available, as well as an + upgrade guide if you were using the previous incarnation of the comments application.. Refactored documentation diff --git a/docs/releases/1.0-porting-guide.txt b/docs/releases/1.0-porting-guide.txt index a6a6bfe7d9..29e40b2ebe 100644 --- a/docs/releases/1.0-porting-guide.txt +++ b/docs/releases/1.0-porting-guide.txt @@ -391,8 +391,8 @@ Comments -------- If you were using Django 0.96's ``django.contrib.comments`` app, you'll need to -upgrade to the new comments app introduced in 1.0. See -:doc:`/ref/contrib/comments/upgrade` for details. +upgrade to the new comments app introduced in 1.0. See the upgrade guide +for details. Template tags ------------- diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index 1e44f57de8..be61311232 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -194,8 +194,8 @@ Refactored ``django.contrib.comments`` As part of a Google Summer of Code project, Thejaswi Puthraya carried out a major rewrite and refactoring of Django's bundled comment system, greatly increasing its flexibility and customizability. :doc:`Full documentation -</ref/contrib/comments/index>` is available, as well as :doc:`an upgrade guide -</ref/contrib/comments/upgrade>` if you were using the previous incarnation of +</ref/contrib/comments/index>` is available, as well as an upgrade guide +if you were using the previous incarnation of the comments application. Removal of deprecated features diff --git a/docs/releases/1.5-alpha-1.txt b/docs/releases/1.5-alpha-1.txt index 8f027c6859..8fbeafc68b 100644 --- a/docs/releases/1.5-alpha-1.txt +++ b/docs/releases/1.5-alpha-1.txt @@ -51,8 +51,8 @@ Other notable new features in Django 1.5 include: * `Support for saving a subset of model's fields`_ - :meth:`Model.save() <django.db.models.Model.save()>` now accepts an ``update_fields`` argument, letting you specify which fields are - written back to the databse when you call ``save()``. This can help - in high-concurrancy operations, and can improve performance. + written back to the database when you call ``save()``. This can help + in high-concurrency operations, and can improve performance. * Better `support for streaming responses <#explicit-streaming-responses>`_ via the new :class:`~django.http.StreamingHttpResponse` response class. @@ -83,7 +83,7 @@ Python compatibility ==================== Django 1.5 requires Python 2.6.5 or above, though we **highly recommended** -Python 2.7.3 or above. Support for Python 2.5 and below as been dropped. +Python 2.7.3 or above. Support for Python 2.5 and below has been dropped. This change should affect only a small number of Django users, as most operating-system vendors today are shipping Python 2.6 or newer as their default @@ -106,17 +106,17 @@ need to install a different version of Django on Python 3. This means that you can write application targeted for just Python 2, just Python 3, or single applications that support both platforms. -However, we're labling this support "experimental" for now: although it's -receved extensive testing via our automated test suite, it's recieved very +However, we're labeling this support "experimental" for now: although it's +received extensive testing via our automated test suite, it's received very little real-world testing. We've done our best to eliminate bugs, but we can't be sure we covered all possible uses of Django. Further, Django's more than a web framework; it's an ecosystem of pluggable components. At this point, very -few third-party applications have been ported to Python 3, so it's unliukely -that a real-world application will have all its dependecies satisfied under +few third-party applications have been ported to Python 3, so it's unlikely +that a real-world application will have all its dependencies satisfied under Python 3. Thus, we're recommending that Django 1.5 not be used in production under Python -3. Instead, use this oportunity to begin :doc:`porting applications to Python 3 +3. Instead, use this opportunity to begin :doc:`porting applications to Python 3 </topics/python3>`. If you're an author of a pluggable component, we encourage you to start porting now. @@ -207,7 +207,7 @@ Retrieval of ``ContentType`` instances associated with proxy models The methods :meth:`ContentTypeManager.get_for_model() <django.contrib.contenttypes.models.ContentTypeManager.get_for_model()>` and :meth:`ContentTypeManager.get_for_models() <django.contrib.contenttypes.models.ContentTypeManager.get_for_models()>` have a new keyword argument – respectively ``for_concrete_model`` and ``for_concrete_models``. -By passing ``False`` using this argument it is now possible to retreive the +By passing ``False`` using this argument it is now possible to retrieve the :class:`ContentType <django.contrib.contenttypes.models.ContentType>` associated with proxy models. @@ -495,8 +495,8 @@ In order to be able to run unit tests in any order and to make sure they are always isolated from each other, :class:`~django.test.TransactionTestCase` will now reset the database *after* each test run instead. -No more implict DB sequences reset -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +No more implicit DB sequences reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :class:`~django.test.TransactionTestCase` tests used to reset primary key sequences automatically together with the database flushing actions described @@ -554,7 +554,7 @@ Miscellaneous available at :func:`django.utils.html.remove_tags`. * Uploaded files are no longer created as executable by default. If you need - them to be executeable change :setting:`FILE_UPLOAD_PERMISSIONS` to your + them to be executable change :setting:`FILE_UPLOAD_PERMISSIONS` to your needs. The new default value is `0666` (octal) and the current umask value is first masked out. diff --git a/docs/releases/1.5-beta-1.txt b/docs/releases/1.5-beta-1.txt new file mode 100644 index 0000000000..f3bfc2a8fa --- /dev/null +++ b/docs/releases/1.5-beta-1.txt @@ -0,0 +1,702 @@ +============================= +Django 1.5 beta release notes +============================= + +November 27, 2012. + +Welcome to Django 1.5 beta! + +This is the second in a series of preview/development releases leading +up to the eventual release of Django 1.5, scheduled for Decemeber +2012. This release is primarily targeted at developers who are +interested in trying out new features and testing the Django codebase +to help identify and resolve bugs prior to the final 1.5 release. + +As such, this release is *not* intended for production use, and any such use +is discouraged. + +These release notes cover the `new features`_, as well +as some `backwards incompatible changes`_ you'll want to be aware of +when upgrading from Django 1.4 or older versions. We've also dropped some +features, which are detailed in :doc:`our deprecation plan +</internals/deprecation>`, and we've `begun the deprecation process for some +features`_. + +.. _`new features`: `What's new in Django 1.5`_ +.. _`backwards incompatible changes`: `Backwards incompatible changes in 1.5`_ +.. _`begun the deprecation process for some features`: `Features deprecated in 1.5`_ + +Overview +======== + +The biggest new feature in Django 1.5 is the `configurable User model`_. Before +Django 1.5, applications that wanted to use Django's auth framework +(:mod:`django.contrib.auth`) were forced to use Django's definition of a "user". +In Django 1.5, you can now swap out the ``User`` model for one that you write +yourself. This could be a simple extension to the existing ``User`` model -- for +example, you could add a Twitter or Facebook ID field -- or you could completely +replace the ``User`` with one totally customized for your site. + +Django 1.5 is also the first release with `Python 3 support`_! We're labeling +this support "experimental" because we don't yet consider it production-ready, +but everything's in place for you to start porting your apps to Python 3. +Our next release, Django 1.6, will support Python 3 without reservations. + +Other notable new features in Django 1.5 include: + +* `Support for saving a subset of model's fields`_ - + :meth:`Model.save() <django.db.models.Model.save()>` now accepts an + ``update_fields`` argument, letting you specify which fields are + written back to the database when you call ``save()``. This can help + in high-concurrency operations, and can improve performance. + +* Better `support for streaming responses <#explicit-streaming-responses-beta-1>`_ via + the new :class:`~django.http.StreamingHttpResponse` response class. + +* `GeoDjango`_ now supports PostGIS 2.0. + +* ... and more; `see below <#what-s-new-in-django-1-5>`_. + +Wherever possible we try to introduce new features in a backwards-compatible +manner per :doc:`our API stability policy </misc/api-stability>`. +However, as with previous releases, Django 1.5 ships with some minor +`backwards incompatible changes`_; people upgrading from previous versions +of Django should read that list carefully. + +One deprecated feature worth noting is the shift to "new-style" :ttag:`url` tag. +Prior to Django 1.3, syntax like ``{% url myview %}`` was interpreted +incorrectly (Django considered ``"myview"`` to be a literal name of a view, not +a template variable named ``myview``). Django 1.3 and above introduced the +``{% load url from future %}`` syntax to bring in the corrected behavior where +``myview`` was seen as a variable. + +The upshot of this is that if you are not using ``{% load url from future %}`` +in your templates, you'll need to change tags like ``{% url myview %}`` to +``{% url "myview" %}``. If you *were* using ``{% load url from future %}`` you +can simply remove that line under Django 1.5 + +Python compatibility +==================== + +Django 1.5 requires Python 2.6.5 or above, though we **highly recommend** +Python 2.7.3 or above. Support for Python 2.5 and below has been dropped. + +This change should affect only a small number of Django users, as most +operating-system vendors today are shipping Python 2.6 or newer as their default +version. If you're still using Python 2.5, however, you'll need to stick to +Django 1.4 until you can upgrade your Python version. Per :doc:`our support +policy </internals/release-process>`, Django 1.4 will continue to receive +security support until the release of Django 1.6. + +Django 1.5 does not run on a Jython final release, because Jython's latest +release doesn't currently support Python 2.6. However, Jython currently does +offer an alpha release featuring 2.7 support, and Django 1.5 supports that alpha +release. + +Python 3 support +~~~~~~~~~~~~~~~~ + +Django 1.5 introduces support for Python 3 - specifically, Python +3.2 and above. This comes in the form of a **single** codebase; you don't +need to install a different version of Django on Python 3. This means that +you can write applications targeted for just Python 2, just Python 3, or single +applications that support both platforms. + +However, we're labeling this support "experimental" for now: although it's +received extensive testing via our automated test suite, it's received very +little real-world testing. We've done our best to eliminate bugs, but we can't +be sure we covered all possible uses of Django. Further, Django's more than a +web framework; it's an ecosystem of pluggable components. At this point, very +few third-party applications have been ported to Python 3, so it's unlikely +that a real-world application will have all its dependencies satisfied under +Python 3. + +Thus, we're recommending that Django 1.5 not be used in production under Python +3. Instead, use this opportunity to begin :doc:`porting applications to Python 3 +</topics/python3>`. If you're an author of a pluggable component, we encourage you +to start porting now. + +We plan to offer first-class, production-ready support for Python 3 in our next +release, Django 1.6. + +What's new in Django 1.5 +======================== + +Configurable User model +~~~~~~~~~~~~~~~~~~~~~~~ + +In Django 1.5, you can now use your own model as the store for user-related +data. If your project needs a username with more than 30 characters, or if +you want to store user's names in a format other than first name/last name, +or you want to put custom profile information onto your User object, you can +now do so. + +If you have a third-party reusable application that references the User model, +you may need to make some changes to the way you reference User instances. You +should also document any specific features of the User model that your +application relies upon. + +See the :ref:`documentation on custom User models <auth-custom-user>` for +more details. + +Support for saving a subset of model's fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The method :meth:`Model.save() <django.db.models.Model.save()>` has a new +keyword argument ``update_fields``. By using this argument it is possible to +save only a select list of model's fields. This can be useful for performance +reasons or when trying to avoid overwriting concurrent changes. + +Deferred instances (those loaded by .only() or .defer()) will automatically +save just the loaded fields. If any field is set manually after load, that +field will also get updated on save. + +See the :meth:`Model.save() <django.db.models.Model.save()>` documentation for +more details. + +Caching of related model instances +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When traversing relations, the ORM will avoid re-fetching objects that were +previously loaded. For example, with the tutorial's models:: + + >>> first_poll = Poll.objects.all()[0] + >>> first_choice = first_poll.choice_set.all()[0] + >>> first_choice.poll is first_poll + True + +In Django 1.5, the third line no longer triggers a new SQL query to fetch +``first_choice.poll``; it was set by the second line. + +For one-to-one relationships, both sides can be cached. For many-to-one +relationships, only the single side of the relationship can be cached. This +is particularly helpful in combination with ``prefetch_related``. + +.. _explicit-streaming-responses-beta-1: + +Explicit support for streaming responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before Django 1.5, it was possible to create a streaming response by passing +an iterator to :class:`~django.http.HttpResponse`. But this was unreliable: +any middleware that accessed the :attr:`~django.http.HttpResponse.content` +attribute would consume the iterator prematurely. + +You can now explicitly generate a streaming response with the new +:class:`~django.http.StreamingHttpResponse` class. This class exposes a +:class:`~django.http.StreamingHttpResponse.streaming_content` attribute which +is an iterator. + +Since :class:`~django.http.StreamingHttpResponse` does not have a ``content`` +attribute, middleware that needs access to the response content must test for +streaming responses and behave accordingly. See :ref:`response-middleware` for +more information. + +``{% verbatim %}`` template tag +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To make it easier to deal with javascript templates which collide with Django's +syntax, you can now use the :ttag:`verbatim` block tag to avoid parsing the +tag's content. + +Retrieval of ``ContentType`` instances associated with proxy models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The methods :meth:`ContentTypeManager.get_for_model() <django.contrib.contenttypes.models.ContentTypeManager.get_for_model()>` +and :meth:`ContentTypeManager.get_for_models() <django.contrib.contenttypes.models.ContentTypeManager.get_for_models()>` +have a new keyword argument – respectively ``for_concrete_model`` and ``for_concrete_models``. +By passing ``False`` using this argument it is now possible to retrieve the +:class:`ContentType <django.contrib.contenttypes.models.ContentType>` +associated with proxy models. + +New ``view`` variable in class-based views context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In all :doc:`generic class-based views </topics/class-based-views/index>` +(or any class-based view inheriting from ``ContextMixin``), the context dictionary +contains a ``view`` variable that points to the ``View`` instance. + +GeoDjango +~~~~~~~~~ + +* :class:`~django.contrib.gis.geos.LineString` and + :class:`~django.contrib.gis.geos.MultiLineString` GEOS objects now support the + :meth:`~django.contrib.gis.geos.GEOSGeometry.interpolate()` and + :meth:`~django.contrib.gis.geos.GEOSGeometry.project()` methods + (so-called linear referencing). + +* The wkb and hex properties of `GEOSGeometry` objects preserve the Z dimension. + +* Support for PostGIS 2.0 has been added and support for GDAL < 1.5 has been + dropped. + +Minor features +~~~~~~~~~~~~~~ + +Django 1.5 also includes several smaller improvements worth noting: + +* The template engine now interprets ``True``, ``False`` and ``None`` as the + corresponding Python objects. + +* :mod:`django.utils.timezone` provides a helper for converting aware + datetimes between time zones. See :func:`~django.utils.timezone.localtime`. + +* The generic views support OPTIONS requests. + +* Management commands do not raise ``SystemExit`` any more when called by code + from :ref:`call_command <call-command>`. Any exception raised by the command + (mostly :ref:`CommandError <ref-command-exceptions>`) is propagated. + +* The dumpdata management command outputs one row at a time, preventing + out-of-memory errors when dumping large datasets. + +* In the localflavor for Canada, "pq" was added to the acceptable codes for + Quebec. It's an old abbreviation. + +* The :ref:`receiver <connecting-receiver-functions>` decorator is now able to + connect to more than one signal by supplying a list of signals. + +* In the admin, you can now filter users by groups which they are members of. + +* :meth:`QuerySet.bulk_create() + <django.db.models.query.QuerySet.bulk_create>` now has a batch_size + argument. By default the batch_size is unlimited except for SQLite where + single batch is limited so that 999 parameters per query isn't exceeded. + +* The :setting:`LOGIN_URL` and :setting:`LOGIN_REDIRECT_URL` settings now also + accept view function names and + :ref:`named URL patterns <naming-url-patterns>`. This allows you to reduce + configuration duplication. More information can be found in the + :func:`~django.contrib.auth.decorators.login_required` documentation. + +* Django now provides a mod_wsgi :doc:`auth handler + </howto/deployment/wsgi/apache-auth>`. + +* The :meth:`QuerySet.delete() <django.db.models.query.QuerySet.delete>` + and :meth:`Model.delete() <django.db.models.Model.delete()>` can now take + fast-path in some cases. The fast-path allows for less queries and less + objects fetched into memory. See :meth:`QuerySet.delete() + <django.db.models.query.QuerySet.delete>` for details. + +* An instance of :class:`~django.core.urlresolvers.ResolverMatch` is stored on + the request as ``resolver_match``. + +* By default, all logging messages reaching the `django` logger when + :setting:`DEBUG` is `True` are sent to the console (unless you redefine the + logger in your :setting:`LOGGING` setting). + +* When using :class:`~django.template.RequestContext`, it is now possible to + look up permissions by using ``{% if 'someapp.someperm' in perms %}`` + in templates. + +* It's not required any more to have ``404.html`` and ``500.html`` templates in + the root templates directory. Django will output some basic error messages for + both situations when those templates are not found. Of course, it's still + recommended as good practice to provide those templates in order to present + pretty error pages to the user. + +* :mod:`django.contrib.auth` provides a new signal that is emitted + whenever a user fails to login successfully. See + :data:`~django.contrib.auth.signals.user_login_failed` + +* The loaddata management command now supports an `ignorenonexistent` option to + ignore data for fields that no longer exist. + +* :meth:`~django.test.SimpleTestCase.assertXMLEqual` and + :meth:`~django.test.SimpleTestCase.assertXMLNotEqual` new assertions allow + you to test equality for XML content at a semantic level, without caring for + syntax differences (spaces, attribute order, etc.). + +* RemoteUserMiddleware now forces logout when the REMOTE_USER header + disappears during the same browser session. + +* The :ref:`cache-based session backend <cached-sessions-backend>` can store + session data in a non-default cache. + +* Multi-column indexes can now be created on models. Read the + :attr:`~django.db.models.Options.index_together` documentation for more + information. + +* During Django's logging configuration verbose Deprecation warnings are + enabled and warnings are captured into the logging system. Logged warnings + are routed through the ``console`` logging handler, which by default requires + :setting:`DEBUG` to be True for output to be generated. The result is that + DeprecationWarnings should be printed to the console in development + environments the way they have been in Python versions < 2.7. + +* The API for :meth:`django.contrib.admin.ModelAdmin.message_user` method has + been modified to accept additional arguments adding capabilities similar to + :func:`django.contrib.messages.add_message`. This is useful for generating + error messages from admin actions. + +* The admin's list filters can now be customized per-request thanks to the new + :meth:`django.contrib.admin.ModelAdmin.get_list_filter` method. + +Backwards incompatible changes in 1.5 +===================================== + +.. warning:: + + In addition to the changes outlined in this section, be sure to review the + :doc:`deprecation plan </internals/deprecation>` for any features that + have been removed. If you haven't updated your code within the + deprecation timeline for a given feature, its removal may appear as a + backwards incompatible change. + +Context in year archive class-based views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For consistency with the other date-based generic views, +:class:`~django.views.generic.dates.YearArchiveView` now passes ``year`` in +the context as a :class:`datetime.date` rather than a string. If you are +using ``{{ year }}`` in your templates, you must replace it with ``{{ +year|date:"Y" }}``. + +``next_year`` and ``previous_year`` were also added in the context. They are +calculated according to ``allow_empty`` and ``allow_future``. + +Context in year and month archive class-based views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:class:`~django.views.generic.dates.YearArchiveView` and +:class:`~django.views.generic.dates.MonthArchiveView` were documented to +provide a ``date_list`` sorted in ascending order in the context, like their +function-based predecessors, but it actually was in descending order. In 1.5, +the documented order was restored. You may want to add (or remove) the +``reversed`` keyword when you're iterating on ``date_list`` in a template:: + + {% for date in date_list reversed %} + +:class:`~django.views.generic.dates.ArchiveIndexView` still provides a +``date_list`` in descending order. + +Context in TemplateView +~~~~~~~~~~~~~~~~~~~~~~~ + +For consistency with the design of the other generic views, +:class:`~django.views.generic.base.TemplateView` no longer passes a ``params`` +dictionary into the context, instead passing the variables from the URLconf +directly into the context. + +Non-form data in HTTP requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:attr:`request.POST <django.http.HttpRequest.POST>` will no longer include data +posted via HTTP requests with non form-specific content-types in the header. +In prior versions, data posted with content-types other than +``multipart/form-data`` or ``application/x-www-form-urlencoded`` would still +end up represented in the :attr:`request.POST <django.http.HttpRequest.POST>` +attribute. Developers wishing to access the raw POST data for these cases, +should use the :attr:`request.body <django.http.HttpRequest.body>` attribute +instead. + +OPTIONS, PUT and DELETE requests in the test client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Unlike GET and POST, these HTTP methods aren't implemented by web browsers. +Rather, they're used in APIs, which transfer data in various formats such as +JSON or XML. Since such requests may contain arbitrary data, Django doesn't +attempt to decode their body. + +However, the test client used to build a query string for OPTIONS and DELETE +requests like for GET, and a request body for PUT requests like for POST. This +encoding was arbitrary and inconsistent with Django's behavior when it +receives the requests, so it was removed in Django 1.5. + +If you were using the ``data`` parameter in an OPTIONS or a DELETE request, +you must convert it to a query string and append it to the ``path`` parameter. + +If you were using the ``data`` parameter in a PUT request without a +``content_type``, you must encode your data before passing it to the test +client and set the ``content_type`` argument. + +.. _simplejson-incompatibilities-beta-1: + +System version of :mod:`simplejson` no longer used +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:ref:`As explained below <simplejson-deprecation-beta-1>`, Django 1.5 deprecates +:mod:`django.utils.simplejson` in favor of Python 2.6's built-in :mod:`json` +module. In theory, this change is harmless. Unfortunately, because of +incompatibilities between versions of :mod:`simplejson`, it may trigger errors +in some circumstances. + +JSON-related features in Django 1.4 always used :mod:`django.utils.simplejson`. +This module was actually: + +- A system version of :mod:`simplejson`, if one was available (ie. ``import + simplejson`` works), if it was more recent than Django's built-in copy or it + had the C speedups, or +- The :mod:`json` module from the standard library, if it was available (ie. + Python 2.6 or greater), or +- A built-in copy of version 2.0.7 of :mod:`simplejson`. + +In Django 1.5, those features use Python's :mod:`json` module, which is based +on version 2.0.9 of :mod:`simplejson`. + +There are no known incompatibilities between Django's copy of version 2.0.7 and +Python's copy of version 2.0.9. However, there are some incompatibilities +between other versions of :mod:`simplejson`: + +- While the :mod:`simplejson` API is documented as always returning unicode + strings, the optional C implementation can return a byte string. This was + fixed in Python 2.7. +- :class:`simplejson.JSONEncoder` gained a ``namedtuple_as_object`` keyword + argument in version 2.2. + +More information on these incompatibilities is available in `ticket #18023`_. + +The net result is that, if you have installed :mod:`simplejson` and your code +uses Django's serialization internals directly -- for instance +:class:`django.core.serializers.json.DjangoJSONEncoder`, the switch from +:mod:`simplejson` to :mod:`json` could break your code. (In general, changes to +internals aren't documented; we're making an exception here.) + +At this point, the maintainers of Django believe that using :mod:`json` from +the standard library offers the strongest guarantee of backwards-compatibility. +They recommend to use it from now on. + +.. _ticket #18023: https://code.djangoproject.com/ticket/18023#comment:10 + +String types of hasher method parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have written a :ref:`custom password hasher <auth_password_storage>`, +your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept +Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the +hashing methods need byte strings, you can use the +:func:`~django.utils.encoding.force_bytes` utility to encode the strings. + +Validation of previous_page_number and next_page_number +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using :doc:`object pagination </topics/pagination>`, +the ``previous_page_number()`` and ``next_page_number()`` methods of the +:class:`~django.core.paginator.Page` object did not check if the returned +number was inside the existing page range. +It does check it now and raises an :exc:`InvalidPage` exception when the number +is either too low or too high. + +Behavior of autocommit database option on PostgreSQL changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PostgreSQL's autocommit option didn't work as advertised previously. It did +work for single transaction block, but after the first block was left the +autocommit behavior was never restored. This bug is now fixed in 1.5. While +this is only a bug fix, it is worth checking your applications behavior if +you are using PostgreSQL together with the autocommit option. + +Session not saved on 500 responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django's session middleware will skip saving the session data if the +response's status code is 500. + +Email checks on failed admin login +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Prior to Django 1.5, if you attempted to log into the admin interface and +mistakenly used your email address instead of your username, the admin +interface would provide a warning advising that your email address was +not your username. In Django 1.5, the introduction of +:ref:`custom User models <auth-custom-user>` has required the removal of this +warning. This doesn't change the login behavior of the admin site; it only +affects the warning message that is displayed under one particular mode of +login failure. + +Changes in tests execution +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some changes have been introduced in the execution of tests that might be +backward-incompatible for some testing setups: + +Database flushing in ``django.test.TransactionTestCase`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the test database was truncated *before* each test run in a +:class:`~django.test.TransactionTestCase`. + +In order to be able to run unit tests in any order and to make sure they are +always isolated from each other, :class:`~django.test.TransactionTestCase` will +now reset the database *after* each test run instead. + +No more implicit DB sequences reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:class:`~django.test.TransactionTestCase` tests used to reset primary key +sequences automatically together with the database flushing actions described +above. + +This has been changed so no sequences are implicitly reset. This can cause +:class:`~django.test.TransactionTestCase` tests that depend on hard-coded +primary key values to break. + +The new :attr:`~django.test.TransactionTestCase.reset_sequences` attribute can +be used to force the old behavior for :class:`~django.test.TransactionTestCase` +that might need it. + +Ordering of tests +^^^^^^^^^^^^^^^^^ + +In order to make sure all ``TestCase`` code starts with a clean database, +tests are now executed in the following order: + +* First, all unittests (including :class:`unittest.TestCase`, + :class:`~django.test.SimpleTestCase`, :class:`~django.test.TestCase` and + :class:`~django.test.TransactionTestCase`) are run with no particular ordering + guaranteed nor enforced among them. + +* Then any other tests (e.g. doctests) that may alter the database without + restoring it to its original state are run. + +This should not cause any problems unless you have existing doctests which +assume a :class:`~django.test.TransactionTestCase` executed earlier left some +database state behind or unit tests that rely on some form of state being +preserved after the execution of other tests. Such tests are already very +fragile, and must now be changed to be able to run independently. + +`cleaned_data` dictionary kept for invalid forms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :attr:`~django.forms.Form.cleaned_data` dictionary is now always present +after form validation. When the form doesn't validate, it contains only the +fields that passed validation. You should test the success of the validation +with the :meth:`~django.forms.Form.is_valid()` method and not with the +presence or absence of the :attr:`~django.forms.Form.cleaned_data` attribute +on the form. + +Behavior of :djadmin:`syncdb` with multiple databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:djadmin:`syncdb` now queries the database routers to determine if content +types (when :mod:`~django.contrib.contenttypes` is enabled) and permissions +(when :mod:`~django.contrib.auth` is enabled) should be created in the target +database. Previously, it created them in the default database, even when +another database was specified with the :djadminopt:`--database` option. + +If you use :djadmin:`syncdb` on multiple databases, you should ensure that +your routers allow synchronizing content types and permissions to only one of +them. See the docs on the :ref:`behavior of contrib apps with multiple +databases <contrib_app_multiple_databases>` for more information. + +Miscellaneous +~~~~~~~~~~~~~ + +* :class:`django.forms.ModelMultipleChoiceField` now returns an empty + ``QuerySet`` as the empty value instead of an empty list. + +* :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError` + instead of :exc:`ValueError` for non-integer inputs. + +* The ``slugify`` template filter is now available as a standard python + function at :func:`django.utils.text.slugify`. Similarly, ``remove_tags`` is + available at :func:`django.utils.html.remove_tags`. + +* Uploaded files are no longer created as executable by default. If you need + them to be executable change :setting:`FILE_UPLOAD_PERMISSIONS` to your + needs. The new default value is `0666` (octal) and the current umask value + is first masked out. + +* The :ref:`F() expressions <query-expressions>` supported bitwise operators by + ``&`` and ``|``. These operators are now available using ``.bitand()`` and + ``.bitor()`` instead. The removal of ``&`` and ``|`` was done to be consistent with + :ref:`Q() expressions <complex-lookups-with-q>` and ``QuerySet`` combining where + the operators are used as boolean AND and OR operators. + +* In a ``filter()`` call, when :ref:`F() expressions <query-expressions>` + contained lookups spanning multi-valued relations, they didn't always reuse + the same relations as other lookups along the same chain. This was changed, + and now F() expressions will always use the same relations as other lookups + within the same ``filter()`` call. + +* The :ttag:`csrf_token` template tag is no longer enclosed in a div. If you need + HTML validation against pre-HTML5 Strict DTDs, you should add a div around it + in your pages. + +* The template tags library ``adminmedia``, which only contained the + deprecated template tag ``{% admin_media_prefix %}``, was removed. + Attempting to load it with ``{% load adminmedia %}`` will fail. If your + templates still contain that line you must remove it. + +Features deprecated in 1.5 +========================== + +.. _simplejson-deprecation-beta-1: + +:setting:`AUTH_PROFILE_MODULE` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +With the introduction of :ref:`custom User models <auth-custom-user>`, there is +no longer any need for a built-in mechanism to store user profile data. + +You can still define user profiles models that have a one-to-one relation with +the User model - in fact, for many applications needing to associate data with +a User account, this will be an appropriate design pattern to follow. However, +the :setting:`AUTH_PROFILE_MODULE` setting, and the +:meth:`~django.contrib.auth.models.User.get_profile()` method for accessing +the user profile model, should not be used any longer. + +Streaming behavior of :class:`HttpResponse` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Django 1.5 deprecates the ability to stream a response by passing an iterator +to :class:`~django.http.HttpResponse`. If you rely on this behavior, switch to +:class:`~django.http.StreamingHttpResponse`. See +:ref:`explicit-streaming-responses-beta-1` above. + +In Django 1.7 and above, the iterator will be consumed immediately by +:class:`~django.http.HttpResponse`. + +``django.utils.simplejson`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since Django 1.5 drops support for Python 2.5, we can now rely on the +:mod:`json` module being available in Python's standard library, so we've +removed our own copy of :mod:`simplejson`. You should now import :mod:`json` +instead :mod:`django.utils.simplejson`. + +Unfortunately, this change might have unwanted side-effects, because of +incompatibilities between versions of :mod:`simplejson` -- see the +:ref:`backwards-incompatible changes <simplejson-incompatibilities-beta-1>` section. +If you rely on features added to :mod:`simplejson` after it became Python's +:mod:`json`, you should import :mod:`simplejson` explicitly. + +``django.utils.encoding.StrAndUnicode`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated. +Define a ``__str__`` method and apply the +:func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead. + +``django.utils.itercompat.product`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :func:`~django.utils.itercompat.product` function has been deprecated. Use +the built-in :func:`itertools.product` instead. + +``django.utils.markup`` +~~~~~~~~~~~~~~~~~~~~~~~ + +The markup contrib module has been deprecated and will follow an accelerated +deprecation schedule. Direct use of python markup libraries or 3rd party tag +libraries is preferred to Django maintaining this functionality in the +framework. + +``cleanup`` management command +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :djadmin:`cleanup` management command has been deprecated and replaced by +:djadmin:`clearsessions`. + +``daily_cleanup.py`` script +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The undocumented ``daily_cleanup.py`` script has been deprecated. Use the +:djadmin:`clearsessions` management command instead. + +``depth`` keyword argument in ``select_related`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``depth`` keyword argument in +:meth:`~django.db.models.query.QuerySet.select_related` has been deprecated. +You should use field names instead. diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index a0ce3cc7a4..6ac5120617 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -47,7 +47,7 @@ Other notable new features in Django 1.5 include: * ... and more; `see below <#what-s-new-in-django-1-5>`_. Wherever possible we try to introduce new features in a backwards-compatible -manner per :doc:`our API stability policy </misc/api-stability>` policy. +manner per :doc:`our API stability policy </misc/api-stability>`. However, as with previous releases, Django 1.5 ships with some minor `backwards incompatible changes`_; people upgrading from previous versions of Django should read that list carefully. @@ -68,7 +68,7 @@ Python compatibility ==================== Django 1.5 requires Python 2.6.5 or above, though we **highly recommended** -Python 2.7.3 or above. Support for Python 2.5 and below as been dropped. +Python 2.7.3 or above. Support for Python 2.5 and below has been dropped. This change should affect only a small number of Django users, as most operating-system vendors today are shipping Python 2.6 or newer as their default @@ -88,7 +88,7 @@ Python 3 support Django 1.5 introduces support for Python 3 - specifically, Python 3.2 and above. This comes in the form of a **single** codebase; you don't need to install a different version of Django on Python 3. This means that -you can write application targeted for just Python 2, just Python 3, or single +you can write applications targeted for just Python 2, just Python 3, or single applications that support both platforms. However, we're labeling this support "experimental" for now: although it's @@ -219,6 +219,15 @@ GeoDjango * Support for PostGIS 2.0 has been added and support for GDAL < 1.5 has been dropped. +New tutorials +~~~~~~~~~~~~~ + +Additions to the docs include a revamped :doc:`Tutorial 3</intro/tutorial03>` +and a new :doc:`tutorial on testing</intro/tutorial05>`. A new section, +"Advanced Tutorials", offers :doc:`How to write reusable apps +</intro/reusable-apps>` as well as a step-by-step guide for new contributors in +:doc:`Writing your first patch for Django </intro/contributing>`. + Minor features ~~~~~~~~~~~~~~ @@ -296,6 +305,31 @@ Django 1.5 also includes several smaller improvements worth noting: you to test equality for XML content at a semantic level, without caring for syntax differences (spaces, attribute order, etc.). +* RemoteUserMiddleware now forces logout when the REMOTE_USER header + disappears during the same browser session. + +* The :ref:`cache-based session backend <cached-sessions-backend>` can store + session data in a non-default cache. + +* Multi-column indexes can now be created on models. Read the + :attr:`~django.db.models.Options.index_together` documentation for more + information. + +* During Django's logging configuration verbose Deprecation warnings are + enabled and warnings are captured into the logging system. Logged warnings + are routed through the ``console`` logging handler, which by default requires + :setting:`DEBUG` to be True for output to be generated. The result is that + DeprecationWarnings should be printed to the console in development + environments the way they have been in Python versions < 2.7. + +* The API for :meth:`django.contrib.admin.ModelAdmin.message_user` method has + been modified to accept additional arguments adding capabilities similar to + :func:`django.contrib.messages.add_message`. This is useful for generating + error messages from admin actions. + +* The admin's list filters can now be customized per-request thanks to the new + :meth:`django.contrib.admin.ModelAdmin.get_list_filter` method. + Backwards incompatible changes in 1.5 ===================================== @@ -307,6 +341,21 @@ Backwards incompatible changes in 1.5 deprecation timeline for a given feature, its removal may appear as a backwards incompatible change. +Managers on abstract models +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Abstract models are able to define a custom manager, and that manager +:ref:`will be inherited by any concrete models extending the abstract model +<custom-managers-and-inheritance>`. However, if you try to use the abstract +model to call a method on the manager, an exception will now be raised. +Previously, the call would have been permitted, but would have failed as soon +as any database operation was attempted (usually with a "table does not exist" +error from the database). + +If you have functionality on a manager that you have been invoking using +the abstract class, you should migrate that logic to a Python +``staticmethod`` or ``classmethod`` on the abstract class. + Context in year archive class-based views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -529,6 +578,20 @@ with the :meth:`~django.forms.Form.is_valid()` method and not with the presence or absence of the :attr:`~django.forms.Form.cleaned_data` attribute on the form. +Behavior of :djadmin:`syncdb` with multiple databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:djadmin:`syncdb` now queries the database routers to determine if content +types (when :mod:`~django.contrib.contenttypes` is enabled) and permissions +(when :mod:`~django.contrib.auth` is enabled) should be created in the target +database. Previously, it created them in the default database, even when +another database was specified with the :djadminopt:`--database` option. + +If you use :djadmin:`syncdb` on multiple databases, you should ensure that +your routers allow synchronizing content types and permissions to only one of +them. See the docs on the :ref:`behavior of contrib apps with multiple +databases <contrib_app_multiple_databases>` for more information. + Miscellaneous ~~~~~~~~~~~~~ @@ -553,10 +616,21 @@ Miscellaneous :ref:`Q() expressions <complex-lookups-with-q>` and ``QuerySet`` combining where the operators are used as boolean AND and OR operators. +* In a ``filter()`` call, when :ref:`F() expressions <query-expressions>` + contained lookups spanning multi-valued relations, they didn't always reuse + the same relations as other lookups along the same chain. This was changed, + and now F() expressions will always use the same relations as other lookups + within the same ``filter()`` call. + * The :ttag:`csrf_token` template tag is no longer enclosed in a div. If you need HTML validation against pre-HTML5 Strict DTDs, you should add a div around it in your pages. +* The template tags library ``adminmedia``, which only contained the + deprecated template tag ``{% admin_media_prefix %}``, was removed. + Attempting to load it with ``{% load adminmedia %}`` will fail. If your + templates still contain that line you must remove it. + Features deprecated in 1.5 ========================== @@ -613,7 +687,6 @@ Define a ``__str__`` method and apply the The :func:`~django.utils.itercompat.product` function has been deprecated. Use the built-in :func:`itertools.product` instead. - ``django.utils.markup`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -621,3 +694,22 @@ The markup contrib module has been deprecated and will follow an accelerated deprecation schedule. Direct use of python markup libraries or 3rd party tag libraries is preferred to Django maintaining this functionality in the framework. + +``cleanup`` management command +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :djadmin:`cleanup` management command has been deprecated and replaced by +:djadmin:`clearsessions`. + +``daily_cleanup.py`` script +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The undocumented ``daily_cleanup.py`` script has been deprecated. Use the +:djadmin:`clearsessions` management command instead. + +``depth`` keyword argument in ``select_related`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``depth`` keyword argument in +:meth:`~django.db.models.query.QuerySet.select_related` has been deprecated. +You should use field names instead. diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt new file mode 100644 index 0000000000..1f57913397 --- /dev/null +++ b/docs/releases/1.6.txt @@ -0,0 +1,43 @@ +============================================ +Django 1.6 release notes - UNDER DEVELOPMENT +============================================ + +Welcome to Django 1.6! + +These release notes cover the `new features`_, as well as some `backwards +incompatible changes`_ you'll want to be aware of when upgrading from Django +1.5 or older versions. We've also dropped some features, which are detailed in +:doc:`our deprecation plan </internals/deprecation>`, and we've `begun the +deprecation process for some features`_. + +.. _`new features`: `What's new in Django 1.6`_ +.. _`backwards incompatible changes`: `Backwards incompatible changes in 1.6`_ +.. _`begun the deprecation process for some features`: `Features deprecated in 1.6`_ + +What's new in Django 1.6 +======================== + +Minor features +~~~~~~~~~~~~~~ + +* Authentication backends can raise ``PermissionDenied`` to immediately fail + the authentication chain. + +* The ``assertQuerysetEqual()`` now checks for undefined order and raises + ``ValueError`` if undefined order is spotted. The order is seen as + undefined if the given ``QuerySet`` isn't ordered and there are more than + one ordered values to compare against. + +Backwards incompatible changes in 1.6 +===================================== + +.. warning:: + + In addition to the changes outlined in this section, be sure to review the + :doc:`deprecation plan </internals/deprecation>` for any features that + have been removed. If you haven't updated your code within the + deprecation timeline for a given feature, its removal may appear as a + backwards incompatible change. + +Features deprecated in 1.6 +========================== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6df9821f56..b0cd87a168 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -16,6 +16,13 @@ Final releases .. _development_release_notes: +1.6 release +----------- +.. toctree:: + :maxdepth: 1 + + 1.6 + 1.5 release ----------- .. toctree:: @@ -92,6 +99,7 @@ notes. .. toctree:: :maxdepth: 1 + 1.5-beta-1 1.5-alpha-1 1.4-beta-1 1.4-alpha-1 diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.graffle b/docs/topics/_images/django_unittest_classes_hierarchy.graffle new file mode 100644 index 0000000000..7211c0f3be --- /dev/null +++ b/docs/topics/_images/django_unittest_classes_hierarchy.graffle @@ -0,0 +1,883 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>ActiveLayerIndex</key> + <integer>0</integer> + <key>ApplicationVersion</key> + <array> + <string>com.omnigroup.OmniGrafflePro</string> + <string>139.16.0.171715</string> + </array> + <key>AutoAdjust</key> + <true/> + <key>BackgroundGraphic</key> + <dict> + <key>Bounds</key> + <string>{{0, 0}, {559.28997802734375, 782.8900146484375}}</string> + <key>Class</key> + <string>SolidGraphic</string> + <key>ID</key> + <integer>2</integer> + <key>Style</key> + <dict> + <key>shadow</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>stroke</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + </dict> + </dict> + <key>BaseZoom</key> + <integer>0</integer> + <key>CanvasOrigin</key> + <string>{0, 0}</string> + <key>ColumnAlign</key> + <integer>1</integer> + <key>ColumnSpacing</key> + <real>36</real> + <key>CreationDate</key> + <string>2012-12-16 18:52:14 +0000</string> + <key>Creator</key> + <string>Aymeric Augustin</string> + <key>DisplayScale</key> + <string>1.000 cm = 1.000 cm</string> + <key>GraphDocumentVersion</key> + <integer>8</integer> + <key>GraphicsList</key> + <array> + <dict> + <key>Class</key> + <string>LineGraphic</string> + <key>Head</key> + <dict> + <key>ID</key> + <integer>8</integer> + </dict> + <key>ID</key> + <integer>29</integer> + <key>OrthogonalBarAutomatic</key> + <true/> + <key>OrthogonalBarPoint</key> + <string>{0, 0}</string> + <key>OrthogonalBarPosition</key> + <real>-1</real> + <key>Points</key> + <array> + <string>{369, 459}</string> + <string>{216, 400.5}</string> + </array> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>HeadArrow</key> + <string>UMLInheritance</string> + <key>HeadScale</key> + <real>0.79999995231628418</real> + <key>Legacy</key> + <true/> + <key>LineType</key> + <integer>2</integer> + <key>TailArrow</key> + <string>0</string> + </dict> + </dict> + <key>Tail</key> + <dict> + <key>ID</key> + <integer>6</integer> + <key>Info</key> + <integer>2</integer> + </dict> + </dict> + <dict> + <key>Class</key> + <string>LineGraphic</string> + <key>Head</key> + <dict> + <key>ID</key> + <integer>12</integer> + <key>Info</key> + <integer>1</integer> + </dict> + <key>ID</key> + <integer>27</integer> + <key>OrthogonalBarAutomatic</key> + <true/> + <key>OrthogonalBarPoint</key> + <string>{0, 0}</string> + <key>OrthogonalBarPosition</key> + <real>-1</real> + <key>Points</key> + <array> + <string>{135, 270}</string> + <string>{369, 225}</string> + </array> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>HeadArrow</key> + <string>UMLInheritance</string> + <key>HeadScale</key> + <real>0.79999995231628418</real> + <key>Legacy</key> + <true/> + <key>LineType</key> + <integer>2</integer> + <key>TailArrow</key> + <string>0</string> + </dict> + </dict> + <key>Tail</key> + <dict> + <key>ID</key> + <integer>26</integer> + <key>Position</key> + <real>0.5</real> + </dict> + </dict> + <dict> + <key>Class</key> + <string>LineGraphic</string> + <key>Head</key> + <dict> + <key>ID</key> + <integer>10</integer> + </dict> + <key>ID</key> + <integer>26</integer> + <key>OrthogonalBarAutomatic</key> + <true/> + <key>OrthogonalBarPoint</key> + <string>{0, 0}</string> + <key>OrthogonalBarPosition</key> + <real>-1</real> + <key>Points</key> + <array> + <string>{135, 315}</string> + <string>{135, 225}</string> + </array> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>HeadArrow</key> + <string>UMLInheritance</string> + <key>HeadScale</key> + <real>0.79999995231628418</real> + <key>Legacy</key> + <true/> + <key>LineType</key> + <integer>2</integer> + <key>TailArrow</key> + <string>0</string> + </dict> + </dict> + <key>Tail</key> + <dict> + <key>ID</key> + <integer>9</integer> + </dict> + </dict> + <dict> + <key>Class</key> + <string>LineGraphic</string> + <key>Head</key> + <dict> + <key>ID</key> + <integer>9</integer> + </dict> + <key>ID</key> + <integer>25</integer> + <key>OrthogonalBarAutomatic</key> + <true/> + <key>OrthogonalBarPoint</key> + <string>{0, 0}</string> + <key>OrthogonalBarPosition</key> + <real>-1</real> + <key>Points</key> + <array> + <string>{135, 387}</string> + <string>{135, 342}</string> + </array> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>HeadArrow</key> + <string>UMLInheritance</string> + <key>HeadScale</key> + <real>0.79999995231628418</real> + <key>Legacy</key> + <true/> + <key>LineType</key> + <integer>2</integer> + <key>TailArrow</key> + <string>0</string> + </dict> + </dict> + <key>Tail</key> + <dict> + <key>ID</key> + <integer>8</integer> + </dict> + </dict> + <dict> + <key>Class</key> + <string>LineGraphic</string> + <key>Head</key> + <dict> + <key>ID</key> + <integer>8</integer> + </dict> + <key>ID</key> + <integer>23</integer> + <key>OrthogonalBarAutomatic</key> + <true/> + <key>OrthogonalBarPoint</key> + <string>{0, 0}</string> + <key>OrthogonalBarPosition</key> + <real>-1</real> + <key>Points</key> + <array> + <string>{135, 459}</string> + <string>{135, 414}</string> + </array> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>HeadArrow</key> + <string>UMLInheritance</string> + <key>HeadScale</key> + <real>0.79999995231628418</real> + <key>Legacy</key> + <true/> + <key>LineType</key> + <integer>2</integer> + <key>TailArrow</key> + <string>0</string> + </dict> + </dict> + <key>Tail</key> + <dict> + <key>ID</key> + <integer>7</integer> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{378, 252}, {81, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>Helvetica</string> + <key>Size</key> + <real>12</real> + </dict> + <key>ID</key> + <integer>22</integer> + <key>Shape</key> + <string>NoteShape</string> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.501961</string> + <key>r</key> + <string>0</string> + </dict> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\i\fs24 \cf2 Python < 2.7}</string> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>TextRelativeArea</key> + <string>{{0, 0}, {1, 1}}</string> + </dict> + <dict> + <key>Bounds</key> + <string>{{45, 252}, {81, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>Helvetica</string> + <key>Size</key> + <real>12</real> + </dict> + <key>ID</key> + <integer>20</integer> + <key>Shape</key> + <string>NoteShape</string> + <key>Style</key> + <dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.501961</string> + <key>r</key> + <string>0</string> + </dict> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\i\fs24 \cf2 Python \uc0\u8805 2.7}</string> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{288, 198}, {162, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>12</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TestCase}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{54, 198}, {162, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>10</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TestCase}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{54, 315}, {162, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>9</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 SimpleTestCase}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{54, 387}, {162, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>8</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TransactionTestCase}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{54, 459}, {162, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>7</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 TestCase}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{288, 459}, {162, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>6</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 LiveServerTestCase}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{18, 297}, {468, 207}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>13</integer> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict/> + <key>Text</key> + <dict> + <key>Align</key> + <integer>2</integer> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr + +\f0\fs24 \cf0 django.test}</string> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + </dict> + <dict> + <key>Bounds</key> + <string>{{18, 153}, {225, 90}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>18</integer> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict/> + <key>Text</key> + <dict> + <key>Align</key> + <integer>2</integer> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr + +\f0\fs24 \cf0 django.utils.unittest\ += unittest (standard library)}</string> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + </dict> + <dict> + <key>Bounds</key> + <string>{{261, 153}, {225, 90}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>19</integer> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict/> + <key>Text</key> + <dict> + <key>Align</key> + <integer>2</integer> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr + +\f0\fs24 \cf0 django.utils.unittest\ += unittest2 (bundled copy)}</string> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + </dict> + </array> + <key>GridInfo</key> + <dict> + <key>ShowsGrid</key> + <string>YES</string> + <key>SnapsToGrid</key> + <string>YES</string> + </dict> + <key>GuidesLocked</key> + <string>NO</string> + <key>GuidesVisible</key> + <string>YES</string> + <key>HPages</key> + <integer>1</integer> + <key>ImageCounter</key> + <integer>1</integer> + <key>KeepToScale</key> + <false/> + <key>Layers</key> + <array> + <dict> + <key>Lock</key> + <string>NO</string> + <key>Name</key> + <string>Calque 1</string> + <key>Print</key> + <string>YES</string> + <key>View</key> + <string>YES</string> + </dict> + </array> + <key>LayoutInfo</key> + <dict> + <key>Animate</key> + <string>NO</string> + <key>circoMinDist</key> + <real>18</real> + <key>circoSeparation</key> + <real>0.0</real> + <key>layoutEngine</key> + <string>dot</string> + <key>neatoSeparation</key> + <real>0.0</real> + <key>twopiSeparation</key> + <real>0.0</real> + </dict> + <key>LinksVisible</key> + <string>NO</string> + <key>MagnetsVisible</key> + <string>NO</string> + <key>MasterSheets</key> + <array/> + <key>ModificationDate</key> + <string>2012-12-16 19:08:28 +0000</string> + <key>Modifier</key> + <string>Aymeric Augustin</string> + <key>NotesVisible</key> + <string>NO</string> + <key>Orientation</key> + <integer>2</integer> + <key>OriginVisible</key> + <string>NO</string> + <key>PageBreaks</key> + <string>YES</string> + <key>PrintInfo</key> + <dict> + <key>NSBottomMargin</key> + <array> + <string>float</string> + <string>41</string> + </array> + <key>NSHorizonalPagination</key> + <array> + <string>coded</string> + <string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string> + </array> + <key>NSLeftMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + <key>NSPaperSize</key> + <array> + <string>size</string> + <string>{595.28997802734375, 841.8900146484375}</string> + </array> + <key>NSPrintReverseOrientation</key> + <array> + <string>int</string> + <string>0</string> + </array> + <key>NSRightMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + <key>NSTopMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + </dict> + <key>PrintOnePage</key> + <false/> + <key>ReadOnly</key> + <string>NO</string> + <key>RowAlign</key> + <integer>1</integer> + <key>RowSpacing</key> + <real>36</real> + <key>SheetTitle</key> + <string>Canevas 1</string> + <key>SmartAlignmentGuidesActive</key> + <string>YES</string> + <key>SmartDistanceGuidesActive</key> + <string>YES</string> + <key>UniqueID</key> + <integer>1</integer> + <key>UseEntirePage</key> + <false/> + <key>VPages</key> + <integer>1</integer> + <key>WindowInfo</key> + <dict> + <key>CurrentSheet</key> + <integer>0</integer> + <key>ExpandedCanvases</key> + <array/> + <key>Frame</key> + <string>{{9, 4}, {694, 874}}</string> + <key>ListView</key> + <true/> + <key>OutlineWidth</key> + <integer>142</integer> + <key>RightSidebar</key> + <false/> + <key>ShowRuler</key> + <true/> + <key>Sidebar</key> + <true/> + <key>SidebarWidth</key> + <integer>120</integer> + <key>VisibleRegion</key> + <string>{{0, 0}, {559, 735}}</string> + <key>Zoom</key> + <real>1</real> + <key>ZoomValues</key> + <array> + <array> + <string>Canevas 1</string> + <real>1</real> + <real>1</real> + </array> + </array> + </dict> +</dict> +</plist> diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.pdf b/docs/topics/_images/django_unittest_classes_hierarchy.pdf Binary files differnew file mode 100644 index 0000000000..cedaba22ac --- /dev/null +++ b/docs/topics/_images/django_unittest_classes_hierarchy.pdf diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.png b/docs/topics/_images/django_unittest_classes_hierarchy.png Binary files differdeleted file mode 100644 index 5f28b56ed6..0000000000 --- a/docs/topics/_images/django_unittest_classes_hierarchy.png +++ /dev/null diff --git a/docs/topics/_images/django_unittest_classes_hierarchy.svg b/docs/topics/_images/django_unittest_classes_hierarchy.svg new file mode 100644 index 0000000000..0482f044dd --- /dev/null +++ b/docs/topics/_images/django_unittest_classes_hierarchy.svg @@ -0,0 +1,3 @@ +<?xml version="1.0"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="-2 137 508 391" width="508pt" height="391pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-12-16 19:08Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.2 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><font-face font-family="Courier" font-size="12" units-per-em="1000" underline-position="-178.22266" underline-thickness="57.617188" slope="0" x-height="462.40234" cap-height="594.72656" ascent="753.90625" descent="-246.09375" font-weight="500"><font-face-src><font-face-name name="Courier"/></font-face-src></font-face><linearGradient x1="0" x2="1" id="Gradient" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="white"/><stop offset="1" stop-color="#aaa"/></linearGradient><linearGradient id="Obj_Gradient" xl:href="#Gradient" gradientTransform="translate(369 459) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_2" xl:href="#Gradient" gradientTransform="translate(135 459) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_3" xl:href="#Gradient" gradientTransform="translate(135 387) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_4" xl:href="#Gradient" gradientTransform="translate(135 315) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_5" xl:href="#Gradient" gradientTransform="translate(135 198) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_6" xl:href="#Gradient" gradientTransform="translate(369 198) rotate(90) scale(27)"/><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="-1e3" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-style="italic" font-weight="500"><font-face-src><font-face-name name="Helvetica-Oblique"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="UMLInheritance_Marker" viewBox="-1 -7 12 14" markerWidth="12" markerHeight="14" color="black"><g><path d="M 9.5999994 0 L 0 -5.5999997 L 0 5.5999997 Z" fill="none" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canevas 1</title><rect fill="white" width="559.28998" height="782.89"/><g><title>Calque 1</title><g><use xl:href="#id19_Graphic" filter="url(#Shadow)"/><use xl:href="#id18_Graphic" filter="url(#Shadow)"/><use xl:href="#id13_Graphic" filter="url(#Shadow)"/><use xl:href="#id6_Graphic" filter="url(#Shadow)"/><use xl:href="#id7_Graphic" filter="url(#Shadow)"/><use xl:href="#id8_Graphic" filter="url(#Shadow)"/><use xl:href="#id9_Graphic" filter="url(#Shadow)"/><use xl:href="#id10_Graphic" filter="url(#Shadow)"/><use xl:href="#id12_Graphic" filter="url(#Shadow)"/><use xl:href="#id20_Graphic" filter="url(#Shadow)"/><use xl:href="#id22_Graphic" filter="url(#Shadow)"/></g><g id="id19_Graphic"><rect x="261" y="153" width="225" height="90" fill="white"/><rect x="261" y="153" width="225" height="90" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(266 158)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="63.77539" y="11" textLength="151.22461">django.utils.unittest</tspan><tspan font-family="Courier" font-size="12" font-weight="500" x="27.769531" y="25" textLength="187.23047">= unittest2 (bundled copy)</tspan></text></g><g id="id18_Graphic"><rect x="18" y="153" width="225" height="90" fill="white"/><rect x="18" y="153" width="225" height="90" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(23 158)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="63.77539" y="11" textLength="151.22461">django.utils.unittest</tspan><tspan font-family="Courier" font-size="12" font-weight="500" x="6.1660156" y="25" textLength="208.83398">= unittest (standard library)</tspan></text></g><g id="id13_Graphic"><rect x="18" y="297" width="468" height="207" fill="white"/><rect x="18" y="297" width="468" height="207" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(23 302)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="378.7871" y="11" textLength="79.21289">django.test</tspan></text></g><g id="id6_Graphic"><path d="M 293 459 L 445 459 C 447.76142 459 450 461.23858 450 464 L 450 481 C 450 483.76142 447.76142 486 445 486 L 293 486 C 290.23858 486 288 483.76142 288 481 C 288 481 288 481 288 481 L 288 464 C 288 461.23858 290.23858 459 293 459 C 293 459 293 459 293 459 Z" fill="url(#Obj_Gradient)"/><path d="M 293 459 L 445 459 C 447.76142 459 450 461.23858 450 464 L 450 481 C 450 483.76142 447.76142 486 445 486 L 293 486 C 290.23858 486 288 483.76142 288 481 C 288 481 288 481 288 481 L 288 464 C 288 461.23858 290.23858 459 293 459 C 293 459 293 459 293 459 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(293 465.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="11.189453" y="11" textLength="129.62109">LiveServerTestCase</tspan></text></g><g id="id7_Graphic"><path d="M 59 459 L 211 459 C 213.76142 459 216 461.23858 216 464 L 216 481 C 216 483.76142 213.76142 486 211 486 L 59 486 C 56.238576 486 54 483.76142 54 481 C 54 481 54 481 54 481 L 54 464 C 54 461.23858 56.238576 459 59 459 C 59 459 59 459 59 459 Z" fill="url(#Obj_Gradient_2)"/><path d="M 59 459 L 211 459 C 213.76142 459 216 461.23858 216 464 L 216 481 C 216 483.76142 213.76142 486 211 486 L 59 486 C 56.238576 486 54 483.76142 54 481 C 54 481 54 481 54 481 L 54 464 C 54 461.23858 56.238576 459 59 459 C 59 459 59 459 59 459 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(59 465.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="47.195312" y="11" textLength="57.609375">TestCase</tspan></text></g><g id="id8_Graphic"><path d="M 59 387 L 211 387 C 213.76142 387 216 389.23858 216 392 L 216 409 C 216 411.76142 213.76142 414 211 414 L 59 414 C 56.238576 414 54 411.76142 54 409 C 54 409 54 409 54 409 L 54 392 C 54 389.23858 56.238576 387 59 387 C 59 387 59 387 59 387 Z" fill="url(#Obj_Gradient_3)"/><path d="M 59 387 L 211 387 C 213.76142 387 216 389.23858 216 392 L 216 409 C 216 411.76142 213.76142 414 211 414 L 59 414 C 56.238576 414 54 411.76142 54 409 C 54 409 54 409 54 409 L 54 392 C 54 389.23858 56.238576 387 59 387 C 59 387 59 387 59 387 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(59 393.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="7.588867" y="11" textLength="136.822266">TransactionTestCase</tspan></text></g><g id="id9_Graphic"><path d="M 59 315 L 211 315 C 213.76142 315 216 317.23858 216 320 L 216 337 C 216 339.76142 213.76142 342 211 342 L 59 342 C 56.238576 342 54 339.76142 54 337 C 54 337 54 337 54 337 L 54 320 C 54 317.23858 56.238576 315 59 315 C 59 315 59 315 59 315 Z" fill="url(#Obj_Gradient_4)"/><path d="M 59 315 L 211 315 C 213.76142 315 216 317.23858 216 320 L 216 337 C 216 339.76142 213.76142 342 211 342 L 59 342 C 56.238576 342 54 339.76142 54 337 C 54 337 54 337 54 337 L 54 320 C 54 317.23858 56.238576 315 59 315 C 59 315 59 315 59 315 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(59 321.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="25.591797" y="11" textLength="100.816406">SimpleTestCase</tspan></text></g><g id="id10_Graphic"><path d="M 59 198 L 211 198 C 213.76142 198 216 200.23858 216 203 L 216 220 C 216 222.76142 213.76142 225 211 225 L 59 225 C 56.238576 225 54 222.76142 54 220 C 54 220 54 220 54 220 L 54 203 C 54 200.23858 56.238576 198 59 198 C 59 198 59 198 59 198 Z" fill="url(#Obj_Gradient_5)"/><path d="M 59 198 L 211 198 C 213.76142 198 216 200.23858 216 203 L 216 220 C 216 222.76142 213.76142 225 211 225 L 59 225 C 56.238576 225 54 222.76142 54 220 C 54 220 54 220 54 220 L 54 203 C 54 200.23858 56.238576 198 59 198 C 59 198 59 198 59 198 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(59 204.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="47.195312" y="11" textLength="57.609375">TestCase</tspan></text></g><g id="id12_Graphic"><path d="M 293 198 L 445 198 C 447.76142 198 450 200.23858 450 203 L 450 220 C 450 222.76142 447.76142 225 445 225 L 293 225 C 290.23858 225 288 222.76142 288 220 C 288 220 288 220 288 220 L 288 203 C 288 200.23858 290.23858 198 293 198 C 293 198 293 198 293 198 Z" fill="url(#Obj_Gradient_6)"/><path d="M 293 198 L 445 198 C 447.76142 198 450 200.23858 450 203 L 450 220 C 450 222.76142 447.76142 225 445 225 L 293 225 C 290.23858 225 288 222.76142 288 220 C 288 220 288 220 288 220 L 288 203 C 288 200.23858 290.23858 198 293 198 C 293 198 293 198 293 198 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(293 204.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="47.195312" y="11" textLength="57.609375">TestCase</tspan></text></g><g id="id20_Graphic"><path d="M 126 259.9893 C 126 257.43159 124.9713 257.11515 117.45693 254.5839 L 117.37755 254.55771 C 109.82349 252 109.74492 252 101.99241 252 C 91.66977 252 45 252 45 252 L 45 279 L 126 279 L 126 259.9893 Z" fill="white"/><path d="M 126 259.9893 C 126 257.43159 124.9713 257.11515 117.45693 254.5839 L 117.37755 254.55771 C 109.82349 252 109.74492 252 101.99241 252 C 91.66977 252 45 252 45 252 L 45 279 L 126 279 L 126 259.9893 Z M 126 259.85754 C 126 257.43159 125.92062 257.43159 109.74492 257.43159 L 109.74492 257.43159 C 109.74492 252.02646 109.74492 252 102.46707 252" stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(50 258.5)" fill="green"><tspan font-family="Helvetica" font-size="12" font-style="italic" font-weight="500" fill="green" x="1.8525391" y="11" textLength="67.29492">Python ≥ 2.7</tspan></text></g><g id="id22_Graphic"><path d="M 459 259.9893 C 459 257.43159 457.9713 257.11515 450.45693 254.5839 L 450.37755 254.55771 C 442.8235 252 442.74492 252 434.9924 252 C 424.66977 252 378 252 378 252 L 378 279 L 459 279 L 459 259.9893 Z" fill="white"/><path d="M 459 259.9893 C 459 257.43159 457.9713 257.11515 450.45693 254.5839 L 450.37755 254.55771 C 442.8235 252 442.74492 252 434.9924 252 C 424.66977 252 378 252 378 252 L 378 279 L 459 279 L 459 259.9893 Z M 459 259.85754 C 459 257.43159 458.92062 257.43159 442.74492 257.43159 L 442.74492 257.43159 C 442.74492 252.02646 442.74492 252 435.46707 252" stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(383 258.5)" fill="green"><tspan font-family="Helvetica" font-size="12" font-style="italic" font-weight="500" fill="green" x="1.6416016" y="11" textLength="67.716797">Python < 2.7</tspan></text></g><path d="M 135 459 L 135 445.9 L 135 427.1 L 135 425.1" marker-end="url(#UMLInheritance_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 135 387 L 135 373.9 L 135 355.1 L 135 353.1" marker-end="url(#UMLInheritance_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 135 315 L 135 301.9 L 135 238.1 L 135 236.1" marker-end="url(#UMLInheritance_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 135 270 L 148.1 270 L 369 270 L 369 238.1 L 369 236.1" marker-end="url(#UMLInheritance_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 369 459 L 369 445.9 L 369 400.5 L 229.1 400.5 L 227.1 400.5" marker-end="url(#UMLInheritance_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g></g></svg> diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 41159984f6..3e2b6bbdbf 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -408,7 +408,7 @@ installation supports. The first entry in this list (that is, ``settings.PASSWORD_HASHERS[0]``) will be used to store passwords, and all the other entries are valid hashers that can be used to check existing passwords. This means that if you want to use a different algorithm, you'll need to modify -:setting:`PASSWORD_HASHERS` to list your prefered algorithm first in the list. +:setting:`PASSWORD_HASHERS` to list your preferred algorithm first in the list. The default for :setting:`PASSWORD_HASHERS` is:: @@ -1889,7 +1889,7 @@ password resets. You must then provide some key implementation details: as the identifying field:: class MyUser(AbstractBaseUser): - identfier = models.CharField(max_length=40, unique=True, db_index=True) + identifier = models.CharField(max_length=40, unique=True, db_index=True) ... USERNAME_FIELD = 'identifier' @@ -1911,6 +1911,15 @@ password resets. You must then provide some key implementation details: ``REQUIRED_FIELDS`` must contain all required fields on your User model, but should *not* contain the ``USERNAME_FIELD``. + .. attribute:: User.is_active + + A boolean attribute that indicates whether the user is considered + "active". This attribute is provided as an attribute on + ``AbstractBaseUser`` defaulting to ``True``. How you choose to + implement it will depend on the details of your chosen auth backends. + See the documentation of the :attr:`attribute on the builtin user model + <django.contrib.auth.models.User.is_active>` for details. + .. method:: User.get_full_name(): A longer formal identifier for the user. A common interpretation @@ -2000,7 +2009,7 @@ additional methods: .. method:: models.CustomUserManager.create_superuser(*username_field*, password, **other_fields) - The prototype of `create_user()` should accept the username field, + The prototype of `create_superuser()` should accept the username field, plus all required fields as arguments. For example, if your user model uses `email` as the username field, and has `date_of_birth` as a required fields, then create_superuser should be defined as:: @@ -2127,6 +2136,76 @@ override any of the definitions that refer to fields on :class:`~django.contrib.auth.models.AbstractUser` that aren't on your custom User class. +Custom users and permissions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To make it easy to include Django's permission framework into your own User +class, Django provides :class:`~django.contrib.auth.model.PermissionsMixin`. +This is an abstract model you can include in the class heirarchy for your User +model, giving you all the methods and database fields necessary to support +Django's permission model. + +:class:`~django.contrib.auth.model.PermissionsMixin` provides the following +methods and attributes: + +.. class:: models.PermissionsMixin + + .. attribute:: models.PermissionsMixin.is_superuser + + Boolean. Designates that this user has all permissions without + explicitly assigning them. + + .. method:: models.PermissionsMixin.get_group_permissions(obj=None) + + Returns a set of permission strings that the user has, through his/her + groups. + + If ``obj`` is passed in, only returns the group permissions for + this specific object. + + .. method:: models.PermissionsMixin.get_all_permissions(obj=None) + + Returns a set of permission strings that the user has, both through + group and user permissions. + + If ``obj`` is passed in, only returns the permissions for this + specific object. + + .. method:: models.PermissionsMixin.has_perm(perm, obj=None) + + Returns ``True`` if the user has the specified permission, where perm is + in the format ``"<app label>.<permission codename>"`` (see + `permissions`_). If the user is inactive, this method will + always return ``False``. + + If ``obj`` is passed in, this method won't check for a permission for + the model, but for this specific object. + + .. method:: models.PermissionsMixin.has_perms(perm_list, obj=None) + + Returns ``True`` if the user has each of the specified permissions, + where each perm is in the format + ``"<app label>.<permission codename>"``. If the user is inactive, + this method will always return ``False``. + + If ``obj`` is passed in, this method won't check for permissions for + the model, but for the specific object. + + .. method:: models.PermissionsMixin.has_module_perms(package_name) + + Returns ``True`` if the user has any permissions in the given package + (the Django app label). If the user is inactive, this method will + always return ``False``. + +.. admonition:: ModelBackend + + If you don't include the + :class:`~django.contrib.auth.model.PermissionsMixin`, you must ensure you + don't invoke the permissions methods on ``ModelBackend``. ``ModelBackend`` + assumes that certain fields are available on your user model. If your User + model doesn't provide those fields, you will receive database errors when + you check permissions. + Custom users and Proxy models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2139,6 +2218,68 @@ If your project uses proxy models, you must either modify the proxy to extend the User model that is currently in use in your project, or merge your proxy's behavior into your User subclass. +Custom users and signals +~~~~~~~~~~~~~~~~~~~~~~~~ + +Another limitation of custom User models is that you can't use +:func:`django.contrib.auth.get_user_model()` as the sender or target of a signal +handler. Instead, you must register the handler with the actual User model. + +Custom users and testing/fixtures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are writing an application that interacts with the User model, you must +take some precautions to ensure that your test suite will run regardless of +the User model that is being used by a project. Any test that instantiates an +instance of User will fail if the User model has been swapped out. This +includes any attempt to create an instance of User with a fixture. + +To ensure that your test suite will pass in any project configuration, +``django.contrib.auth.tests.utils`` defines a ``@skipIfCustomUser`` decorator. +This decorator will cause a test case to be skipped if any User model other +than the default Django user is in use. This decorator can be applied to a +single test, or to an entire test class. + +Depending on your application, tests may also be needed to be added to ensure +that the application works with *any* user model, not just the default User +model. To assist with this, Django provides two substitute user models that +can be used in test suites: + +* :class:`django.contrib.auth.tests.custom_user.CustomUser`, a custom user + model that uses an ``email`` field as the username, and has a basic + admin-compliant permissions setup + +* :class:`django.contrib.auth.tests.custom_user.ExtensionUser`, a custom + user model that extends :class:`~django.contrib.auth.models.AbstractUser`, + adding a ``date_of_birth`` field. + +You can then use the ``@override_settings`` decorator to make that test run +with the custom User model. For example, here is a skeleton for a test that +would test three possible User models -- the default, plus the two User +models provided by ``auth`` app:: + + from django.contrib.auth.tests.utils import skipIfCustomUser + from django.test import TestCase + from django.test.utils import override_settings + + + class ApplicationTestCase(TestCase): + @skipIfCustomUser + def test_normal_user(self): + "Run tests for the normal user model" + self.assertSomething() + + @override_settings(AUTH_USER_MODEL='auth.CustomUser') + def test_custom_user(self): + "Run tests for a custom user model with email-based authentication" + self.assertSomething() + + @override_settings(AUTH_USER_MODEL='auth.ExtensionUser') + def test_extension_user(self): + "Run tests for a simple extension of the built-in User." + self.assertSomething() + + A full example -------------- @@ -2272,15 +2413,21 @@ code would be required in the app's ``admin.py`` file:: class UserChangeForm(forms.ModelForm): - """A form for updateing users. Includes all the fields on + """A form for updating users. Includes all the fields on the user, but replaces the password field with admin's - pasword hash display field. + password hash display field. """ password = ReadOnlyPasswordHashField() class Meta: model = MyUser + def clean_password(self): + # Regardless of what the user provides, return the initial value. + # This is done here, rather than on the field, because the + # field does not have access to the initial value + return self.initial["password"] + class MyUserAdmin(UserAdmin): # The forms to add and change user instances @@ -2376,6 +2523,12 @@ processing at the first positive match. you need to force users to re-authenticate using different methods. A simple way to do that is simply to execute ``Session.objects.all().delete()``. +.. versionadded:: 1.6 + +If a backend raises a :class:`~django.core.exceptions.PermissionDenied` +exception, authentication will immediately fail. Django won't check the +backends that follow. + Writing an authentication backend --------------------------------- diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 2f95c33dd5..a15cf58370 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -785,6 +785,16 @@ nonexistent cache key.:: However, if the backend doesn't natively provide an increment/decrement operation, it will be implemented using a two-step retrieve/update. + +You can close the connection to your cache with ``close()`` if implemented by +the cache backend. + + >>> cache.close() + +.. note:: + + For caches that don't implement ``close`` methods it is a no-op. + .. _cache_key_prefixing: Cache key prefixing diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt index 7bae3c692d..7d12184705 100644 --- a/docs/topics/class-based-views/generic-editing.txt +++ b/docs/topics/class-based-views/generic-editing.txt @@ -90,8 +90,8 @@ class: .. code-block:: python # models.py - from django import models from django.core.urlresolvers import reverse + from django.db import models class Author(models.Model): name = models.CharField(max_length=200) @@ -102,7 +102,7 @@ class: Then we can use :class:`CreateView` and friends to do the actual work. Notice how we're just configuring the generic class-based views here; we don't have to write any logic ourselves:: - + # views.py from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.core.urlresolvers import reverse_lazy @@ -134,7 +134,7 @@ Finally, we hook these new views into the URLconf:: url(r'author/(?P<pk>\d+)/$', AuthorUpdate.as_view(), name='author_update'), url(r'author/(?P<pk>\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'), ) - + .. note:: These views inherit :class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin` @@ -160,8 +160,8 @@ you can use a custom :class:`ModelForm` to do this. First, add the foreign key relation to the model:: # models.py - from django import models from django.contrib.auth import User + from django.db import models class Author(models.Model): name = models.CharField(max_length=200) @@ -177,7 +177,7 @@ Create a custom :class:`ModelForm` in order to exclude the # forms.py from django import forms from myapp.models import Author - + class AuthorForm(forms.ModelForm): class Meta: model = Author @@ -190,7 +190,7 @@ In the view, use the custom :attr:`form_class` and override from django.views.generic.edit import CreateView from myapp.models import Author from myapp.forms import AuthorForm - + class AuthorCreate(CreateView): form_class = AuthorForm model = Author diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index a738221892..54d4b0f252 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -24,9 +24,9 @@ Basic examples Django provides base view classes which will suit a wide range of applications. All views inherit from the :class:`~django.views.generic.base.View` class, which handles linking the view in to the URLs, HTTP method dispatching and other -simple features. :class:`~django.views.generic.base.RedirectView` is for a simple HTTP -redirect, and :class:`~django.views.generic.base.TemplateView` extends the base class -to make it also render a template. +simple features. :class:`~django.views.generic.base.RedirectView` is for a +simple HTTP redirect, and :class:`~django.views.generic.base.TemplateView` +extends the base class to make it also render a template. Simple usage in your URLconf @@ -34,7 +34,8 @@ Simple usage in your URLconf The simplest way to use generic views is to create them directly in your URLconf. If you're only changing a few simple attributes on a class-based view, -you can simply pass them into the ``as_view`` method call itself:: +you can simply pass them into the +:meth:`~django.views.generic.base.View.as_view` method call itself:: from django.conf.urls import patterns, url, include from django.views.generic import TemplateView @@ -43,9 +44,10 @@ you can simply pass them into the ``as_view`` method call itself:: (r'^about/', TemplateView.as_view(template_name="about.html")), ) -Any arguments given will override the ``template_name`` on the -A similar overriding pattern can be used for the ``url`` attribute on -:class:`~django.views.generic.base.RedirectView`. +Any arguments passed to :meth:`~django.views.generic.base.View.as_view` will +override attributes set on the class. In this example, we set ``template_name`` +on the ``TemplateView``. A similar overriding pattern can be used for the +``url`` attribute on :class:`~django.views.generic.base.RedirectView`. Subclassing generic views @@ -67,8 +69,8 @@ and override the template name:: Then we just need to add this new view into our URLconf. `~django.views.generic.base.TemplateView` is a class, not a function, so we -point the URL to the ``as_view`` class method instead, which provides a -function-like entry to class-based views:: +point the URL to the :meth:`~django.views.generic.base.View.as_view` class +method instead, which provides a function-like entry to class-based views:: # urls.py from django.conf.urls import patterns, url, include diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt index eda6f9dac0..a14616a17c 100644 --- a/docs/topics/db/managers.txt +++ b/docs/topics/db/managers.txt @@ -274,6 +274,21 @@ it into the inheritance hierarchy *after* the defaults:: # Default manager is CustomManager, but OtherManager is # also available via the "extra_manager" attribute. +Note that while you can *define* a custom manager on the abstract model, you +can't *invoke* any methods using the abstract model. That is:: + + ClassA.objects.do_something() + +is legal, but:: + + AbstractBase.objects.do_something() + +will raise an exception. This is because managers are intended to encapsulate +logic for managing collections of objects. Since you can't have a collection of +abstract objects, it doesn't make sense to be managing them. If you have +functionality that applies to the abstract model, you should put that functionality +in a ``staticmethod`` or ``classmethod`` on the abstract model. + Implementation concerns ----------------------- diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 84b4c84061..2f1676ac1a 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -673,11 +673,12 @@ For example, this model has a few custom methods:: def baby_boomer_status(self): "Returns the person's baby-boomer status." import datetime - if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31): - return "Baby boomer" if self.birth_date < datetime.date(1945, 8, 1): return "Pre-boomer" - return "Post-boomer" + elif self.birth_date < datetime.date(1965, 1, 1): + return "Baby boomer" + else: + return "Post-boomer" def is_midwestern(self): "Returns True if this person is from the Midwest." diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index d2ff8645a9..8a02305376 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -630,3 +630,49 @@ However, if you're using SQLite or MySQL with MyISAM tables, there is no enforced referential integrity; as a result, you may be able to 'fake' cross database foreign keys. However, this configuration is not officially supported by Django. + +.. _contrib_app_multiple_databases: + +Behavior of contrib apps +------------------------ + +Several contrib apps include models, and some apps depend on others. Since +cross-database relationships are impossible, this creates some restrictions on +how you can split these models across databases: + +- each one of ``contenttypes.ContentType``, ``sessions.Session`` and + ``sites.Site`` can be stored in any database, given a suitable router. +- ``auth`` models — ``User``, ``Group`` and ``Permission`` — are linked + together and linked to ``ContentType``, so they must be stored in the same + database as ``ContentType``. +- ``admin`` and ``comments`` depend on ``auth``, so their models must be in + the same database as ``auth``. +- ``flatpages`` and ``redirects`` depend on ``sites``, so their models must be + in the same database as ``sites``. + +In addition, some objects are automatically created just after +:djadmin:`syncdb` creates a table to hold them in a database: + +- a default ``Site``, +- a ``ContentType`` for each model (including those not stored in that + database), +- three ``Permission`` for each model (including those not stored in that + database). + +.. versionchanged:: 1.5 + Previously, ``ContentType`` and ``Permission`` instances were created only + in the default database. + +For common setups with multiple databases, it isn't useful to have these +objects in more than one database. Common setups include master / slave and +connecting to external databases. Therefore, it's recommended: + +- either to run :djadmin:`syncdb` only for the default database; +- or to write :ref:`database router<topics-db-multi-db-routing>` that allows + synchronizing these three models only to one database. + +.. warning:: + + If you're synchronizing content types to more that one database, be aware + that their primary keys may not match across databases. This may result in + data corruption or data loss. diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 772792d39d..b5cca52e23 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -132,6 +132,41 @@ Write your own :doc:`custom SQL to retrieve data or populate models </topics/db/sql>`. Use ``django.db.connection.queries`` to find out what Django is writing for you and start from there. +Retrieve individual objects using a unique, indexed column +========================================================== + +There are two reasons to use a column with +:attr:`~django.db.models.Field.unique` or +:attr:`~django.db.models.Field.db_index` when using +:meth:`~django.db.models.query.QuerySet.get` to retrieve individual objects. +First, the query will be quicker because of the underlying database index. +Also, the query could run much slower if multiple objects match the lookup; +having a unique constraint on the column guarantees this will never happen. + +So using the :ref:`example Weblog models <queryset-model-example>`:: + + >>> entry = Entry.objects.get(id=10) + +will be quicker than: + + >>> entry = Entry.object.get(headline="News Item Title") + +because ``id`` is indexed by the database and is guaranteed to be unique. + +Doing the following is potentially quite slow: + + >>> entry = Entry.objects.get(headline__startswith="News") + +First of all, `headline` is not indexed, which will make the underlying +database fetch slower. + +Second, the lookup doesn't guarantee that only one object will be returned. +If the query matches more than one object, it will retrieve and transfer all of +them from the database. This penalty could be substantial if hundreds or +thousands of records are returned. The penalty will be compounded if the +database lives on a separate server, where network overhead and latency also +play a factor. + Retrieve everything at once if you know you will need it ======================================================== diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index d826a39562..90c06ac66a 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -417,7 +417,7 @@ translates (roughly) into the following SQL:: There's one exception though, in case of a :class:`~django.db.models.ForeignKey` you can specify the field name suffixed with ``_id``. In this case, the value parameter is expected - to contain the raw value of the foreign model's primary key. For example:: + to contain the raw value of the foreign model's primary key. For example: >>> Entry.objects.filter(blog_id__exact=4) @@ -575,12 +575,17 @@ To select all blogs that contain an entry with *"Lennon"* in the headline Blog.objects.filter(entry__headline__contains='Lennon').filter( entry__pub_date__year=2008) -In this second example, the first filter restricted the queryset to all those -blogs linked to that particular type of entry. The second filter restricted -the set of blogs *further* to those that are also linked to the second type of -entry. The entries select by the second filter may or may not be the same as -the entries in the first filter. We are filtering the ``Blog`` items with each -filter statement, not the ``Entry`` items. +Suppose there is only one blog that had both entries containing *"Lennon"* and +entries from 2008, but that none of the entries from 2008 contained *"Lennon"*. +The first query would not return any blogs, but the second query would return +that one blog. + +In the second example, the first filter restricts the queryset to all those +blogs linked to entries with *"Lennon"* in the headline. The second filter +restricts the set of blogs *further* to those that are also linked to entries +that were published in 2008. The entries selected by the second filter may or +may not be the same as the entries in the first filter. We are filtering the +``Blog`` items with each filter statement, not the ``Entry`` items. All of this behavior also applies to :meth:`~django.db.models.query.QuerySet.exclude`: all the conditions in a @@ -920,7 +925,7 @@ Things get more complicated if you use inheritance. Consider a subclass of class ThemeBlog(Blog): theme = models.CharField(max_length=200) - django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme = 'python') + django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python') django_blog.save() # django_blog.pk == 3 Due to how inheritance works, you have to set both ``pk`` and ``id`` to None:: diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 4a52c5af35..65944abb8b 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -161,8 +161,12 @@ managers, too. transactions. It tells Django you'll be managing the transaction on your own. - If your view changes data and doesn't ``commit()`` or ``rollback()``, - Django will raise a ``TransactionManagementError`` exception. + Whether you are writing or simply reading from the database, you must + ``commit()`` or ``rollback()`` explicitly or Django will raise a + :exc:`TransactionManagementError` exception. This is required when reading + from the database because ``SELECT`` statements may call functions which + modify tables, and thus it is impossible to know if any data has been + modified. Manual transaction management looks like this:: @@ -204,11 +208,13 @@ This applies to all database operations, not just write operations. Even if your transaction only reads from the database, the transaction must be committed or rolled back before you complete a request. +.. _deactivate-transaction-management: + How to globally deactivate transaction management ================================================= Control freaks can totally disable all transaction management by setting -``DISABLE_TRANSACTION_MANAGEMENT`` to ``True`` in the Django settings file. +:setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If you do this, Django won't provide any automatic transaction management whatsoever. Middleware will no longer implicitly commit transactions, and diff --git a/docs/topics/files.txt b/docs/topics/files.txt index c9b4327941..66e104759a 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -139,6 +139,8 @@ useful -- you can use the global default storage system:: See :doc:`/ref/files/storage` for the file storage API. +.. _builtin-fs-storage: + The built-in filesystem storage class ------------------------------------- diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 692be7cd7c..233346db0d 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -90,9 +90,6 @@ Model field Form field ``NullBooleanField`` ``CharField`` -``PhoneNumberField`` ``USPhoneNumberField`` - (from ``django.contrib.localflavor.us``) - ``PositiveIntegerField`` ``IntegerField`` ``PositiveSmallIntegerField`` ``IntegerField`` @@ -192,6 +189,8 @@ we'll discuss in a moment.):: name = forms.CharField(max_length=100) authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all()) +.. _modelform-is-valid-and-errors: + The ``is_valid()`` method and ``errors`` ---------------------------------------- @@ -213,7 +212,9 @@ method. This method creates and saves a database object from the data bound to the form. A subclass of ``ModelForm`` can accept an existing model instance as the keyword argument ``instance``; if this is supplied, ``save()`` will update that instance. If it's not supplied, -``save()`` will create a new instance of the specified model:: +``save()`` will create a new instance of the specified model: + +.. code-block:: python # Create a form instance from POST data. >>> f = ArticleForm(request.POST) @@ -232,8 +233,10 @@ supplied, ``save()`` will update that instance. If it's not supplied, >>> f = ArticleForm(request.POST, instance=a) >>> f.save() -Note that ``save()`` will raise a ``ValueError`` if the data in the form -doesn't validate -- i.e., if form.errors evaluates to True. +Note that if the form :ref:`hasn't been validated +<modelform-is-valid-and-errors>`, calling ``save()`` will do so by checking +``form.errors``. A ``ValueError`` will be raised if the data in the form +doesn't validate -- i.e., if ``form.errors`` evaluates to ``True``. This ``save()`` method accepts an optional ``commit`` keyword argument, which accepts either ``True`` or ``False``. If you call ``save()`` with diff --git a/docs/topics/http/_images/middleware.graffle b/docs/topics/http/_images/middleware.graffle new file mode 100644 index 0000000000..4aa85f7611 --- /dev/null +++ b/docs/topics/http/_images/middleware.graffle @@ -0,0 +1,957 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>ActiveLayerIndex</key> + <integer>0</integer> + <key>ApplicationVersion</key> + <array> + <string>com.omnigroup.OmniGrafflePro</string> + <string>139.16.0.171715</string> + </array> + <key>AutoAdjust</key> + <true/> + <key>BackgroundGraphic</key> + <dict> + <key>Bounds</key> + <string>{{0, 0}, {559.28997802734375, 782.8900146484375}}</string> + <key>Class</key> + <string>SolidGraphic</string> + <key>ID</key> + <integer>2</integer> + <key>Style</key> + <dict> + <key>shadow</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>stroke</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + </dict> + </dict> + <key>BaseZoom</key> + <integer>0</integer> + <key>CanvasOrigin</key> + <string>{0, 0}</string> + <key>ColumnAlign</key> + <integer>1</integer> + <key>ColumnSpacing</key> + <real>36</real> + <key>CreationDate</key> + <string>2012-12-09 18:55:12 +0000</string> + <key>Creator</key> + <string>Aymeric Augustin</string> + <key>DisplayScale</key> + <string>1.000 cm = 1.000 cm</string> + <key>GraphDocumentVersion</key> + <integer>8</integer> + <key>GraphicsList</key> + <array> + <dict> + <key>Bounds</key> + <string>{{144, 405}, {369, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>33</integer> + <key>Shape</key> + <string>Bezier</string> + <key>ShapeData</key> + <dict> + <key>UnitPoints</key> + <array> + <string>{-0.5, -0.5}</string> + <string>{-0.5, -0.5}</string> + <string>{0.47959183673469341, -0.5}</string> + <string>{0.47959183673469408, -0.5}</string> + <string>{0.47959183673469341, -0.5}</string> + <string>{0.5, 0}</string> + <string>{0.5, 0}</string> + <string>{0.5, 0}</string> + <string>{0.47959183673469408, 0.5}</string> + <string>{0.47959183673469408, 0.5}</string> + <string>{0.47959183673469408, 0.5}</string> + <string>{-0.5, 0.5}</string> + <string>{-0.5, 0.5}</string> + <string>{-0.5, 0.5}</string> + <string>{-0.47560975609756084, 0}</string> + <string>{-0.47560975609756084, 0}</string> + <string>{-0.47560975609756084, 0}</string> + <string>{-0.5, -0.5}</string> + </array> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 view function}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{229.5, 238.5}, {297, 36}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>31</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>AdjustableArrow</string> + <key>ShapeData</key> + <dict> + <key>width</key> + <real>27</real> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.8</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.70634919404983521</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.4</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>0.0</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>1</string> + </dict> + <key>Pattern</key> + <integer>1</integer> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;\red255\green0\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf2 process_exception}</string> + </dict> + <key>TextRelativeArea</key> + <string>{{0.125, 0.25}, {0.75, 0.5}}</string> + <key>isConnectedShape</key> + <true/> + </dict> + <dict> + <key>Bounds</key> + <string>{{328.5, 229.5}, {315, 36}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>30</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>AdjustableArrow</string> + <key>ShapeData</key> + <dict> + <key>width</key> + <real>27</real> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.8</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.70634919404983521</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.4</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>0.0</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.501961</string> + <key>r</key> + <string>0</string> + </dict> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf2 process_response}</string> + </dict> + <key>TextRelativeArea</key> + <string>{{0.125, 0.25}, {0.75, 0.5}}</string> + <key>isConnectedShape</key> + <true/> + </dict> + <dict> + <key>Bounds</key> + <string>{{283.5, 238.5}, {297, 36}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>29</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>AdjustableArrow</string> + <key>ShapeData</key> + <dict> + <key>width</key> + <real>27</real> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.8</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.70634919404983521</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.4</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>0.0</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.501961</string> + <key>r</key> + <string>0</string> + </dict> + <key>Pattern</key> + <integer>1</integer> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf2 process_template_response}</string> + </dict> + <key>TextRelativeArea</key> + <string>{{0.125, 0.25}, {0.75, 0.5}}</string> + <key>isConnectedShape</key> + <true/> + </dict> + <dict> + <key>Bounds</key> + <string>{{27, 243}, {288, 36}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>28</integer> + <key>Rotation</key> + <real>90</real> + <key>Shape</key> + <string>AdjustableArrow</string> + <key>ShapeData</key> + <dict> + <key>width</key> + <real>27</real> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.8</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.70634919404983521</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.4</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>0.0</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.501961</string> + <key>r</key> + <string>0</string> + </dict> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf2 process_view}</string> + </dict> + <key>TextRelativeArea</key> + <string>{{0.125, 0.25}, {0.75, 0.5}}</string> + <key>isConnectedShape</key> + <true/> + </dict> + <dict> + <key>Bounds</key> + <string>{{-40.500000000767386, 220.49999999804004}, {297, 36}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>27</integer> + <key>Rotation</key> + <real>90</real> + <key>Shape</key> + <string>AdjustableArrow</string> + <key>ShapeData</key> + <dict> + <key>width</key> + <real>27</real> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.8</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.70634919404983521</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.4</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>0.0</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.501961</string> + <key>r</key> + <string>0</string> + </dict> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;\red0\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf2 process_request}</string> + </dict> + <key>TextRelativeArea</key> + <string>{{0.125, 0.25}, {0.75, 0.5}}</string> + <key>isConnectedShape</key> + <true/> + </dict> + <dict> + <key>Bounds</key> + <string>{{360, 63}, {144, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>12</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 HttpResponse}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{72, 63}, {144, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>11</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 HttpRequest}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{72, 324}, {432, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>10</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 MessageMiddleware}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{72, 279}, {432, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>9</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 AuthenticationMiddleware}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{72, 234}, {432, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>8</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 CsrfViewMiddleware}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{72, 189}, {432, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>7</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 SessionMiddleware}</string> + </dict> + </dict> + <dict> + <key>Bounds</key> + <string>{{72, 144}, {432, 27}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>ID</key> + <integer>6</integer> + <key>Magnets</key> + <array> + <string>{0, 1}</string> + <string>{0, -1}</string> + <string>{1, 0}</string> + <string>{-1, 0}</string> + </array> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientColor</key> + <dict> + <key>w</key> + <string>0.666667</string> + </dict> + </dict> + <key>stroke</key> + <dict> + <key>CornerRadius</key> + <real>5</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340 +\cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc + +\f0\fs24 \cf0 CommonMiddleware}</string> + </dict> + </dict> + </array> + <key>GridInfo</key> + <dict> + <key>ShowsGrid</key> + <string>YES</string> + <key>SnapsToGrid</key> + <string>YES</string> + </dict> + <key>GuidesLocked</key> + <string>NO</string> + <key>GuidesVisible</key> + <string>YES</string> + <key>HPages</key> + <integer>1</integer> + <key>ImageCounter</key> + <integer>1</integer> + <key>KeepToScale</key> + <false/> + <key>Layers</key> + <array> + <dict> + <key>Lock</key> + <string>NO</string> + <key>Name</key> + <string>Calque 1</string> + <key>Print</key> + <string>YES</string> + <key>View</key> + <string>YES</string> + </dict> + </array> + <key>LayoutInfo</key> + <dict> + <key>Animate</key> + <string>NO</string> + <key>circoMinDist</key> + <real>18</real> + <key>circoSeparation</key> + <real>0.0</real> + <key>layoutEngine</key> + <string>dot</string> + <key>neatoSeparation</key> + <real>0.0</real> + <key>twopiSeparation</key> + <real>0.0</real> + </dict> + <key>LinksVisible</key> + <string>NO</string> + <key>MagnetsVisible</key> + <string>NO</string> + <key>MasterSheets</key> + <array/> + <key>ModificationDate</key> + <string>2012-12-09 19:48:54 +0000</string> + <key>Modifier</key> + <string>Aymeric Augustin</string> + <key>NotesVisible</key> + <string>NO</string> + <key>Orientation</key> + <integer>2</integer> + <key>OriginVisible</key> + <string>NO</string> + <key>PageBreaks</key> + <string>YES</string> + <key>PrintInfo</key> + <dict> + <key>NSBottomMargin</key> + <array> + <string>float</string> + <string>41</string> + </array> + <key>NSHorizonalPagination</key> + <array> + <string>coded</string> + <string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG</string> + </array> + <key>NSLeftMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + <key>NSPaperSize</key> + <array> + <string>size</string> + <string>{595.28997802734375, 841.8900146484375}</string> + </array> + <key>NSPrintReverseOrientation</key> + <array> + <string>int</string> + <string>0</string> + </array> + <key>NSRightMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + <key>NSTopMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + </dict> + <key>PrintOnePage</key> + <false/> + <key>ReadOnly</key> + <string>NO</string> + <key>RowAlign</key> + <integer>1</integer> + <key>RowSpacing</key> + <real>36</real> + <key>SheetTitle</key> + <string>Canevas 1</string> + <key>SmartAlignmentGuidesActive</key> + <string>YES</string> + <key>SmartDistanceGuidesActive</key> + <string>YES</string> + <key>UniqueID</key> + <integer>1</integer> + <key>UseEntirePage</key> + <false/> + <key>VPages</key> + <integer>1</integer> + <key>WindowInfo</key> + <dict> + <key>CurrentSheet</key> + <integer>0</integer> + <key>ExpandedCanvases</key> + <array/> + <key>Frame</key> + <string>{{248, 4}, {694, 874}}</string> + <key>ListView</key> + <true/> + <key>OutlineWidth</key> + <integer>142</integer> + <key>RightSidebar</key> + <false/> + <key>ShowRuler</key> + <true/> + <key>Sidebar</key> + <true/> + <key>SidebarWidth</key> + <integer>120</integer> + <key>VisibleRegion</key> + <string>{{0, 0}, {559, 735}}</string> + <key>Zoom</key> + <real>1</real> + <key>ZoomValues</key> + <array> + <array> + <string>Canevas 1</string> + <real>1</real> + <real>1</real> + </array> + </array> + </dict> +</dict> +</plist> diff --git a/docs/topics/http/_images/middleware.pdf b/docs/topics/http/_images/middleware.pdf Binary files differnew file mode 100644 index 0000000000..8a9e61ef40 --- /dev/null +++ b/docs/topics/http/_images/middleware.pdf diff --git a/docs/topics/http/_images/middleware.png b/docs/topics/http/_images/middleware.png Binary files differdeleted file mode 100644 index 505c70ac36..0000000000 --- a/docs/topics/http/_images/middleware.png +++ /dev/null diff --git a/docs/topics/http/_images/middleware.svg b/docs/topics/http/_images/middleware.svg new file mode 100644 index 0000000000..4da8d22da8 --- /dev/null +++ b/docs/topics/http/_images/middleware.svg @@ -0,0 +1,3 @@ +<?xml version="1.0"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="52 47 481 409" width="481pt" height="409pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2012-12-09 19:48Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.2 --></metadata><defs><filter id="Shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="3.488"/><feOffset in="blur" result="offset" dx="0" dy="4"/><feFlood flood-color="black" flood-opacity=".75" result="flood"/><feComposite in="flood" in2="offset" operator="in"/></filter><linearGradient x1="0" x2="1" id="Gradient" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="white"/><stop offset="1" stop-color="#aaa"/></linearGradient><linearGradient id="Obj_Gradient" xl:href="#Gradient" gradientTransform="translate(288 144) rotate(90) scale(27)"/><font-face font-family="Courier" font-size="12" units-per-em="1000" underline-position="-178.22266" underline-thickness="57.617188" slope="0" x-height="462.40234" cap-height="594.72656" ascent="753.90625" descent="-246.09375" font-weight="500"><font-face-src><font-face-name name="Courier"/></font-face-src></font-face><linearGradient id="Obj_Gradient_2" xl:href="#Gradient" gradientTransform="translate(288 189) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_3" xl:href="#Gradient" gradientTransform="translate(288 234) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_4" xl:href="#Gradient" gradientTransform="translate(288 279) rotate(90) scale(27)"/><linearGradient id="Obj_Gradient_5" xl:href="#Gradient" gradientTransform="translate(288 324) rotate(90) scale(27)"/><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canevas 1</title><rect fill="white" width="559.28998" height="782.89"/><g><title>Calque 1</title><g><use xl:href="#id6_Graphic" filter="url(#Shadow)"/><use xl:href="#id7_Graphic" filter="url(#Shadow)"/><use xl:href="#id8_Graphic" filter="url(#Shadow)"/><use xl:href="#id9_Graphic" filter="url(#Shadow)"/><use xl:href="#id10_Graphic" filter="url(#Shadow)"/><use xl:href="#id11_Graphic" filter="url(#Shadow)"/><use xl:href="#id12_Graphic" filter="url(#Shadow)"/><use xl:href="#id33_Graphic" filter="url(#Shadow)"/></g><g id="id6_Graphic"><path d="M 77 144 L 499 144 C 501.76142 144 504 146.23858 504 149 L 504 166 C 504 168.76142 501.76142 171 499 171 L 77 171 C 74.238576 171 72 168.76142 72 166 C 72 166 72 166 72 166 L 72 149 C 72 146.23858 74.238576 144 77 144 C 77 144 77 144 77 144 Z" fill="url(#Obj_Gradient)"/><path d="M 77 144 L 499 144 C 501.76142 144 504 146.23858 504 149 L 504 166 C 504 168.76142 501.76142 171 499 171 L 77 171 C 74.238576 171 72 168.76142 72 166 C 72 166 72 166 72 166 L 72 149 C 72 146.23858 74.238576 144 77 144 C 77 144 77 144 77 144 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(77 150.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" fill="black" x="153.390625" y="11" textLength="115.21875">CommonMiddleware</tspan></text></g><g id="id7_Graphic"><path d="M 77 189 L 499 189 C 501.76142 189 504 191.23858 504 194 L 504 211 C 504 213.76142 501.76142 216 499 216 L 77 216 C 74.238576 216 72 213.76142 72 211 C 72 211 72 211 72 211 L 72 194 C 72 191.23858 74.238576 189 77 189 C 77 189 77 189 77 189 Z" fill="url(#Obj_Gradient_2)"/><path d="M 77 189 L 499 189 C 501.76142 189 504 191.23858 504 194 L 504 211 C 504 213.76142 501.76142 216 499 216 L 77 216 C 74.238576 216 72 213.76142 72 211 C 72 211 72 211 72 211 L 72 194 C 72 191.23858 74.238576 189 77 189 C 77 189 77 189 77 189 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(77 195.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="149.79004" y="11" textLength="122.41992">SessionMiddleware</tspan></text></g><g id="id8_Graphic"><path d="M 77 234 L 499 234 C 501.76142 234 504 236.23858 504 239 L 504 256 C 504 258.76142 501.76142 261 499 261 L 77 261 C 74.238576 261 72 258.76142 72 256 C 72 256 72 256 72 256 L 72 239 C 72 236.23858 74.238576 234 77 234 C 77 234 77 234 77 234 Z" fill="url(#Obj_Gradient_3)"/><path d="M 77 234 L 499 234 C 501.76142 234 504 236.23858 504 239 L 504 256 C 504 258.76142 501.76142 261 499 261 L 77 261 C 74.238576 261 72 258.76142 72 256 C 72 256 72 256 72 256 L 72 239 C 72 236.23858 74.238576 234 77 234 C 77 234 77 234 77 234 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(77 240.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="146.18945" y="11" textLength="129.62109">CsrfViewMiddleware</tspan></text></g><g id="id9_Graphic"><path d="M 77 279 L 499 279 C 501.76142 279 504 281.23858 504 284 L 504 301 C 504 303.76142 501.76142 306 499 306 L 77 306 C 74.238576 306 72 303.76142 72 301 C 72 301 72 301 72 301 L 72 284 C 72 281.23858 74.238576 279 77 279 C 77 279 77 279 77 279 Z" fill="url(#Obj_Gradient_4)"/><path d="M 77 279 L 499 279 C 501.76142 279 504 281.23858 504 284 L 504 301 C 504 303.76142 501.76142 306 499 306 L 77 306 C 74.238576 306 72 303.76142 72 301 C 72 301 72 301 72 301 L 72 284 C 72 281.23858 74.238576 279 77 279 C 77 279 77 279 77 279 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(77 285.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="124.58594" y="11" textLength="172.82812">AuthenticationMiddleware</tspan></text></g><g id="id10_Graphic"><path d="M 77 324 L 499 324 C 501.76142 324 504 326.23858 504 329 L 504 346 C 504 348.76142 501.76142 351 499 351 L 77 351 C 74.238576 351 72 348.76142 72 346 C 72 346 72 346 72 346 L 72 329 C 72 326.23858 74.238576 324 77 324 C 77 324 77 324 77 324 Z" fill="url(#Obj_Gradient_5)"/><path d="M 77 324 L 499 324 C 501.76142 324 504 326.23858 504 329 L 504 346 C 504 348.76142 501.76142 351 499 351 L 77 351 C 74.238576 351 72 348.76142 72 346 C 72 346 72 346 72 346 L 72 329 C 72 326.23858 74.238576 324 77 324 C 77 324 77 324 77 324 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(77 330.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="149.79004" y="11" textLength="122.41992">MessageMiddleware</tspan></text></g><g id="id11_Graphic"><rect x="72" y="63" width="144" height="27" fill="white"/><rect x="72" y="63" width="144" height="27" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(77 69.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="27.393555" y="11" textLength="79.21289">HttpRequest</tspan></text></g><g id="id12_Graphic"><rect x="360" y="63" width="144" height="27" fill="white"/><rect x="360" y="63" width="144" height="27" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(365 69.5)" fill="black"><tspan font-family="Courier" font-size="12" font-weight="500" x="23.792969" y="11" textLength="86.41406">HttpResponse</tspan></text></g><path d="M 99 90 L 117 90 L 117 360 L 126 360 L 108 387 L 90 360 L 99 360 Z" fill="white" fill-opacity=".8"/><path d="M 99 90 L 117 90 L 117 360 L 126 360 L 108 387 L 90 360 L 99 360 Z" stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(115 132.125) rotate(90)" fill="green"><tspan font-family="Courier" font-size="12" font-weight="500" fill="green" x="52.36621" y="11" textLength="108.01758">process_request</tspan></text><path d="M 162 117 L 180 117 L 180 378 L 189 378 L 171 405 L 153 378 L 162 378 Z" fill="white" fill-opacity=".8"/><path d="M 162 117 L 180 117 L 180 378 L 189 378 L 171 405 L 153 378 L 162 378 Z" stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(178 158) rotate(90)" fill="green"><tspan font-family="Courier" font-size="12" font-weight="500" fill="green" x="59.79297" y="11" textLength="86.41406">process_view</tspan></text><path d="M 441 405 L 423 405 L 423 135 L 414 135 L 432 108 L 450 135 L 441 135 Z" fill="white" fill-opacity=".8"/><path d="M 441 405 L 423 405 L 423 135 L 414 135 L 432 108 L 450 135 L 441 135 Z" stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="4,4"/><text transform="translate(425 362.875) rotate(-90)" fill="green"><tspan font-family="Courier" font-size="12" font-weight="500" fill="green" x="16.360352" y="11" textLength="180.0293">process_template_response</tspan></text><path d="M 495 405 L 477 405 L 477 117 L 468 117 L 486 90 L 504 117 L 495 117 Z" fill="white" fill-opacity=".8"/><path d="M 495 405 L 477 405 L 477 117 L 468 117 L 486 90 L 504 117 L 495 117 Z" stroke="green" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(479 360.625) rotate(-90)" fill="green"><tspan font-family="Courier" font-size="12" font-weight="500" fill="green" x="55.515625" y="11" textLength="115.21875">process_response</tspan></text><path d="M 387 405 L 369 405 L 369 135 L 360 135 L 378 108 L 396 135 L 387 135 Z" fill="white" fill-opacity=".8"/><path d="M 387 405 L 369 405 L 369 135 L 360 135 L 378 108 L 396 135 L 387 135 Z" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="4,4"/><text transform="translate(371 362.875) rotate(-90)" fill="red"><tspan font-family="Courier" font-size="12" font-weight="500" fill="red" x="45.16504" y="11" textLength="122.41992">process_exception</tspan></text><g id="id33_Graphic"><path d="M 144 405 L 505.4694 405 L 513 418.5 L 505.4694 432 L 144 432 L 153 418.5 Z" fill="white"/><path d="M 144 405 L 505.4694 405 L 513 418.5 L 505.4694 432 L 144 432 L 153 418.5 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(149 411.5)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="500" fill="black" x="144.81543" y="11" textLength="69.36914">view function</tspan></text></g></g></g></svg> diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index c27e7e8690..18243c77ce 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -4,25 +4,28 @@ Middleware Middleware is a framework of hooks into Django's request/response processing. It's a light, low-level "plugin" system for globally altering Django's input -and/or output. +or output. Each middleware component is responsible for doing some specific function. For -example, Django includes a middleware component, ``XViewMiddleware``, that adds -an ``"X-View"`` HTTP header to every response to a ``HEAD`` request. +example, Django includes a middleware component, +:class:`~django.middleware.transaction.TransactionMiddleware`, that wraps the +processing of each HTTP request in a database transaction. This document explains how middleware works, how you activate middleware, and how to write your own middleware. Django ships with some built-in middleware -you can use right out of the box; they're documented in the :doc:`built-in +you can use right out of the box. They're documented in the :doc:`built-in middleware reference </ref/middleware>`. Activating middleware ===================== -To activate a middleware component, add it to the :setting:`MIDDLEWARE_CLASSES` -list in your Django settings. In :setting:`MIDDLEWARE_CLASSES`, each middleware -component is represented by a string: the full Python path to the middleware's -class name. For example, here's the default :setting:`MIDDLEWARE_CLASSES` -created by :djadmin:`django-admin.py startproject <startproject>`:: +To activate a middleware component, add it to the +:setting:`MIDDLEWARE_CLASSES` tuple in your Django settings. + +In :setting:`MIDDLEWARE_CLASSES`, each middleware component is represented by +a string: the full Python path to the middleware's class name. For example, +here's the default value created by :djadmin:`django-admin.py startproject +<startproject>`:: MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', @@ -32,23 +35,44 @@ created by :djadmin:`django-admin.py startproject <startproject>`:: 'django.contrib.messages.middleware.MessageMiddleware', ) -During the request phases (:meth:`process_request` and :meth:`process_view` -middleware), Django applies middleware in the order it's defined in -:setting:`MIDDLEWARE_CLASSES`, top-down. During the response phases -(:meth:`process_response` and :meth:`process_exception` middleware), the -classes are applied in reverse order, from the bottom up. You can think of it -like an onion: each middleware class is a "layer" that wraps the view: - -.. image:: _images/middleware.png - :width: 502 - :height: 417 - :alt: Middleware application order. - -A Django installation doesn't require any middleware -- e.g., -:setting:`MIDDLEWARE_CLASSES` can be empty, if you'd like -- but it's strongly +A Django installation doesn't require any middleware — +:setting:`MIDDLEWARE_CLASSES` can be empty, if you'd like — but it's strongly suggested that you at least use :class:`~django.middleware.common.CommonMiddleware`. +The order in :setting:`MIDDLEWARE_CLASSES` matters because a middleware can +depend on other middleware. For instance, +:class:`~django.contrib.auth.middleware.AuthenticationMiddleware` stores the +authenticated user in the session; therefore, it must run after +:class:`~django.contrib.sessions.middleware.SessionMiddleware`. + +Hooks and application order +=========================== + +During the request phase, before calling the view, Django applies middleware +in the order it's defined in :setting:`MIDDLEWARE_CLASSES`, top-down. Two +hooks are available: + +* :meth:`process_request` +* :meth:`process_view` + +During the response phase, after calling the view, middleware are applied in +reverse order, from the bottom up. Three hooks are available: + +* :meth:`process_exception` (only if the view raised an exception) +* :meth:`process_template_response` (only for template responses) +* :meth:`process_response` + +.. image:: _images/middleware.* + :alt: middleware application order + :width: 481 + :height: 409 + +If you prefer, you can also think of it like an onion: each middleware class +is a "layer" that wraps the view. + +The behavior of each hook is described below. + Writing your own middleware =========================== @@ -62,16 +86,19 @@ Python class that defines one or more of the following methods: .. method:: process_request(self, request) -``request`` is an :class:`~django.http.HttpRequest` object. This method is -called on each request, before Django decides which view to execute. +``request`` is an :class:`~django.http.HttpRequest` object. + +``process_request()`` is called on each request, before Django decides which +view to execute. -``process_request()`` should return either ``None`` or an -:class:`~django.http.HttpResponse` object. If it returns ``None``, Django will -continue processing this request, executing any other middleware and, then, the -appropriate view. If it returns an :class:`~django.http.HttpResponse` object, -Django won't bother calling ANY other request, view or exception middleware, or -the appropriate view; it'll return that :class:`~django.http.HttpResponse`. -Response middleware is always called on every response. +It should return either ``None`` or an :class:`~django.http.HttpResponse` +object. If it returns ``None``, Django will continue processing this request, +executing any other ``process_request()`` middleware, then, ``process_view()`` +middleware, and finally, the appropriate view. If it returns an +:class:`~django.http.HttpResponse` object, Django won't bother calling any +other request, view or exception middleware, or the appropriate view; it'll +apply response middleware to that :class:`~django.http.HttpResponse`, and +return the result. .. _view-middleware: @@ -88,14 +115,15 @@ dictionary of keyword arguments that will be passed to the view. Neither ``view_args`` nor ``view_kwargs`` include the first view argument (``request``). -``process_view()`` is called just before Django calls the view. It should -return either ``None`` or an :class:`~django.http.HttpResponse` object. If it -returns ``None``, Django will continue processing this request, executing any -other ``process_view()`` middleware and, then, the appropriate view. If it -returns an :class:`~django.http.HttpResponse` object, Django won't bother -calling ANY other request, view or exception middleware, or the appropriate -view; it'll return that :class:`~django.http.HttpResponse`. Response -middleware is always called on every response. +``process_view()`` is called just before Django calls the view. + +It should return either ``None`` or an :class:`~django.http.HttpResponse` +object. If it returns ``None``, Django will continue processing this request, +executing any other ``process_view()`` middleware and, then, the appropriate +view. If it returns an :class:`~django.http.HttpResponse` object, Django won't +bother calling any other view or exception middleware, or the appropriate +view; it'll apply response middleware to that +:class:`~django.http.HttpResponse`, and return the result. .. note:: @@ -119,19 +147,17 @@ middleware is always called on every response. .. method:: process_template_response(self, request, response) -``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is a -subclass of :class:`~django.template.response.SimpleTemplateResponse` (e.g. -:class:`~django.template.response.TemplateResponse`) or any response object -that implements a ``render`` method. +``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is +the :class:`~django.template.response.TemplateResponse` object (or equivalent) +returned by a Django view or by a middleware. -``process_template_response()`` must return a response object that implements a -``render`` method. It could alter the given ``response`` by changing -``response.template_name`` and ``response.context_data``, or it could create -and return a brand-new -:class:`~django.template.response.SimpleTemplateResponse` or equivalent. +``process_template_response()`` is called just after the view has finished +executing, if the response instance has a ``render()`` method, indicating that +it is a :class:`~django.template.response.TemplateResponse` or equivalent. -``process_template_response()`` will only be called if the response -instance has a ``render()`` method, indicating that it is a +It must return a response object that implements a ``render`` method. It could +alter the given ``response`` by changing ``response.template_name`` and +``response.context_data``, or it could create and return a brand-new :class:`~django.template.response.TemplateResponse` or equivalent. You don't need to explicitly render responses -- responses will be @@ -139,7 +165,7 @@ automatically rendered once all template response middleware has been called. Middleware are run in reverse order during the response phase, which -includes process_template_response. +includes ``process_template_response()``. .. _response-middleware: @@ -148,21 +174,34 @@ includes process_template_response. .. method:: process_response(self, request, response) -``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is the -:class:`~django.http.HttpResponse` object returned by a Django view. +``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is +the :class:`~django.http.HttpResponse` or +:class:`~django.http.StreamingHttpResponse` object returned by a Django view +or by a middleware. + +``process_response()`` is called on all responses before they're returned to +the browser. -``process_response()`` must return an :class:`~django.http.HttpResponse` -object. It could alter the given ``response``, or it could create and return a -brand-new :class:`~django.http.HttpResponse`. +It must return an :class:`~django.http.HttpResponse` or +:class:`~django.http.StreamingHttpResponse` object. It could alter the given +``response``, or it could create and return a brand-new +:class:`~django.http.HttpResponse` or +:class:`~django.http.StreamingHttpResponse`. Unlike the ``process_request()`` and ``process_view()`` methods, the -``process_response()`` method is always called, even if the ``process_request()`` -and ``process_view()`` methods of the same middleware class were skipped because -an earlier middleware method returned an :class:`~django.http.HttpResponse` -(this means that your ``process_response()`` method cannot rely on setup done in -``process_request()``, for example). In addition, during the response phase the -classes are applied in reverse order, from the bottom up. This means classes -defined at the end of :setting:`MIDDLEWARE_CLASSES` will be run first. +``process_response()`` method is always called, even if the +``process_request()`` and ``process_view()`` methods of the same middleware +class were skipped (because an earlier middleware method returned an +:class:`~django.http.HttpResponse`). In particular, this means that your +``process_response()`` method cannot rely on setup done in +``process_request()``. + +Finally, remember that during the response phase, middleware are applied in +reverse order, from the bottom up. This means classes defined at the end of +:setting:`MIDDLEWARE_CLASSES` will be run first. + +Dealing with streaming responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionchanged:: 1.5 ``response`` may also be an :class:`~django.http.StreamingHttpResponse` @@ -177,10 +216,17 @@ must test for streaming responses and adjust their behavior accordingly:: if response.streaming: response.streaming_content = wrap_streaming_content(response.streaming_content) else: - response.content = wrap_content(response.content) + response.content = alter_content(response.content) + +.. note:: + + ``streaming_content`` should be assumed to be too large to hold in memory. + Response middleware may wrap it in a new generator, but must not consume + it. Wrapping is typically implemented as follows:: -``streaming_content`` should be assumed to be too large to hold in memory. -Middleware may wrap it in a new generator, but must not consume it. + def wrap_streaming_content(content) + for chunk in content: + yield alter_content(chunk) .. _exception-middleware: @@ -195,8 +241,9 @@ Middleware may wrap it in a new generator, but must not consume it. Django calls ``process_exception()`` when a view raises an exception. ``process_exception()`` should return either ``None`` or an :class:`~django.http.HttpResponse` object. If it returns an -:class:`~django.http.HttpResponse` object, the response will be returned to -the browser. Otherwise, default exception handling kicks in. +:class:`~django.http.HttpResponse` object, the template response and response +middleware will be applied, and the resulting response returned to the +browser. Otherwise, default exception handling kicks in. Again, middleware are run in reverse order during the response phase, which includes ``process_exception``. If an exception middleware returns a response, @@ -221,9 +268,9 @@ Marking middleware as unused ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's sometimes useful to determine at run-time whether a piece of middleware -should be used. In these cases, your middleware's ``__init__`` method may raise -``django.core.exceptions.MiddlewareNotUsed``. Django will then remove that -piece of middleware from the middleware process. +should be used. In these cases, your middleware's ``__init__`` method may +raise :exc:`django.core.exceptions.MiddlewareNotUsed`. Django will then remove +that piece of middleware from the middleware process. Guidelines ---------- diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 15f9f7feba..baf8aa5cb5 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -45,6 +45,8 @@ If you want to use a database-backed session, you need to add Once you have configured your installation, run ``manage.py syncdb`` to install the single database table that stores session data. +.. _cached-sessions-backend: + Using cached sessions --------------------- @@ -62,6 +64,13 @@ sure you've configured your cache; see the :doc:`cache documentation sessions directly instead of sending everything through the file or database cache backends. +If you have multiple caches defined in :setting:`CACHES`, Django will use the +default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the +name of that cache. + +.. versionchanged:: 1.5 + The :setting:`SESSION_CACHE_ALIAS` setting was added. + Once your cache is configured, you've got two choices for how to store data in the cache: @@ -250,17 +259,35 @@ You can edit it multiple times. with no custom expiration (or those set to expire at browser close), this will equal :setting:`SESSION_COOKIE_AGE`. + This function accepts two optional keyword arguments: + + - ``modification``: last modification of the session, as a + :class:`~datetime.datetime` object. Defaults to the current time. + - ``expiry``: expiry information for the session, as a + :class:`~datetime.datetime` object, an :class:`int` (in seconds), or + ``None``. Defaults to the value stored in the session by + :meth:`set_expiry`, if there is one, or ``None``. + .. method:: get_expiry_date Returns the date this session will expire. For sessions with no custom expiration (or those set to expire at browser close), this will equal the date :setting:`SESSION_COOKIE_AGE` seconds from now. + This function accepts the same keyword argumets as :meth:`get_expiry_age`. + .. method:: get_expire_at_browser_close Returns either ``True`` or ``False``, depending on whether the user's session cookie will expire when the user's Web browser is closed. + .. method:: SessionBase.clear_expired + + .. versionadded:: 1.5 + + Removes expired sessions from the session store. This class method is + called by :djadmin:`clearsessions`. + Session object guidelines ------------------------- @@ -447,22 +474,29 @@ This setting is a global default and can be overwritten at a per-session level by explicitly calling the :meth:`~backends.base.SessionBase.set_expiry` method of ``request.session`` as described above in `using sessions in views`_. -Clearing the session table +Clearing the session store ========================== -If you're using the database backend, note that session data can accumulate in -the ``django_session`` database table and Django does *not* provide automatic -purging. Therefore, it's your job to purge expired sessions on a regular basis. +As users create new sessions on your website, session data can accumulate in +your session store. If you're using the database backend, the +``django_session`` database table will grow. If you're using the file backend, +your temporary directory will contain an increasing number of files. -To understand this problem, consider what happens when a user uses a session. +To understand this problem, consider what happens with the database backend. When a user logs in, Django adds a row to the ``django_session`` database table. Django updates this row each time the session data changes. If the user logs out manually, Django deletes the row. But if the user does *not* log out, -the row never gets deleted. +the row never gets deleted. A similar process happens with the file backend. + +Django does *not* provide automatic purging of expired sessions. Therefore, +it's your job to purge expired sessions on a regular basis. Django provides a +clean-up management command for this purpose: :djadmin:`clearsessions`. It's +recommended to call this command on a regular basis, for example as a daily +cron job. -Django provides a sample clean-up script: ``django-admin.py cleanup``. -That script deletes any session in the session table whose ``expire_date`` is -in the past -- but your application may have different requirements. +Note that the cache backend isn't vulnerable to this problem, because caches +automatically delete stale data. Neither is the cookie backend, because the +session data is stored by the users' browsers. Settings ======== diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 0dc38b1459..b1b4700b73 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -4,7 +4,7 @@ Django shortcut functions .. module:: django.shortcuts :synopsis: - Convenience shortcuts that spam multiple levels of Django's MVC stack. + Convenience shortcuts that span multiple levels of Django's MVC stack. .. index:: shortcuts diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index e178df2af2..00c07da6ea 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -552,12 +552,11 @@ layers where URLs are needed: * In templates: Using the :ttag:`url` template tag. -* In Python code: Using the :func:`django.core.urlresolvers.reverse()` +* In Python code: Using the :func:`django.core.urlresolvers.reverse` function. * In higher level code related to handling of URLs of Django model instances: - The :meth:`django.db.models.Model.get_absolute_url()` method and the - :func:`django.db.models.permalink` decorator. + The :meth:`~django.db.models.Model.get_absolute_url` method. Examples -------- @@ -622,10 +621,10 @@ view:: ) This is completely valid, but it leads to problems when you try to do reverse -URL matching (through the :func:`~django.db.models.permalink` decorator or the -:ttag:`url` template tag). Continuing this example, if you wanted to retrieve -the URL for the ``archive`` view, Django's reverse URL matcher would get -confused, because *two* URL patterns point at that view. +URL matching (through the :func:`~django.core.urlresolvers.reverse` function +or the :ttag:`url` template tag). Continuing this example, if you wanted to +retrieve the URL for the ``archive`` view, Django's reverse URL matcher would +get confused, because *two* URL patterns point at that view. To solve this problem, Django supports **named URL patterns**. That is, you can give a name to a URL pattern in order to distinguish it from other patterns @@ -724,7 +723,7 @@ the fully qualified name into parts, and then tries the following lookup: render a template. The current application can also be specified manually as an argument - to the :func:`django.core.urlresolvers.reverse()` function. + to the :func:`django.core.urlresolvers.reverse` function. 3. If there is no current application. Django looks for a default application instance. The default application instance is the instance diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 7c4d1bbb6e..caa2882f37 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -209,6 +209,17 @@ This view loads and renders the template ``403.html`` in your root template directory, or if this file does not exist, instead serves the text "403 Forbidden", as per :rfc:`2616` (the HTTP 1.1 Specification). +``django.views.defaults.permission_denied`` is triggered by a +:exc:`~django.core.exceptions.PermissionDenied` exception. To deny access in a +view you can use code like this:: + + from django.core.exceptions import PermissionDenied + + def edit(request, pk): + if not request.user.is_staff: + raise PermissionDenied + # ... + It is possible to override ``django.views.defaults.permission_denied`` in the same way you can for the 404 and 500 views by specifying a ``handler403`` in your URLconf:: diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 52994ed16a..b71033f319 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -10,7 +10,7 @@ Install Python Being a Python Web framework, Django requires Python. It works with any Python version from 2.6.5 to 2.7. It also features -experimental support for versions 3.2 and 3.3. +experimental support for versions from 3.2.3 to 3.3. Get Python at http://www.python.org. If you're running Linux or Mac OS X, you probably already have it installed. @@ -106,15 +106,15 @@ support channels provided by each 3rd party project. In addition to a database backend, you'll need to make sure your Python database bindings are installed. -* If you're using PostgreSQL, you'll need the ``postgresql_psycopg2`` package. +* If you're using PostgreSQL, you'll need the `postgresql_psycopg2`_ package. You might want to refer to our :ref:`PostgreSQL notes <postgresql-notes>` for further technical details specific to this database. If you're on Windows, check out the unofficial `compiled Windows version`_. -* If you're using MySQL, you'll need the ``MySQL-python`` package, version 1.2.1p2 or higher. You - will also want to read the database-specific :ref:`notes for the MySQL - backend <mysql-notes>`. +* If you're using MySQL, you'll need the ``MySQL-python`` package, version + 1.2.1p2 or higher. You will also want to read the database-specific + :ref:`notes for the MySQL backend <mysql-notes>`. * If you're using Oracle, you'll need a copy of cx_Oracle_, but please read the database-specific :ref:`notes for the Oracle backend <oracle-notes>` @@ -124,21 +124,23 @@ database bindings are installed. * If you're using an unofficial 3rd party backend, please consult the documentation provided for any additional requirements. -If you plan to use Django's ``manage.py syncdb`` command to -automatically create database tables for your models, you'll need to -ensure that Django has permission to create and alter tables in the -database you're using; if you plan to manually create the tables, you -can simply grant Django ``SELECT``, ``INSERT``, ``UPDATE`` and -``DELETE`` permissions. On some databases, Django will need -``ALTER TABLE`` privileges during ``syncdb`` but won't issue -``ALTER TABLE`` statements on a table once ``syncdb`` has created it. +If you plan to use Django's ``manage.py syncdb`` command to automatically +create database tables for your models (after first installing Django and +creating a project), you'll need to ensure that Django has permission to create +and alter tables in the database you're using; if you plan to manually create +the tables, you can simply grant Django ``SELECT``, ``INSERT``, ``UPDATE`` and +``DELETE`` permissions. On some databases, Django will need ``ALTER TABLE`` +privileges during ``syncdb`` but won't issue ``ALTER TABLE`` statements on a +table once ``syncdb`` has created it. After creating a database user with these +permissions, you'll specify the details in your project's settings file, +see :setting:`DATABASES` for details. -If you're using Django's :doc:`testing framework</topics/testing>` to test database queries, -Django will need permission to create a test database. +If you're using Django's :doc:`testing framework</topics/testing>` to test +database queries, Django will need permission to create a test database. .. _PostgreSQL: http://www.postgresql.org/ .. _MySQL: http://www.mysql.com/ -.. _psycopg: http://initd.org/pub/software/psycopg/ +.. _postgresql_psycopg2: http://initd.org/psycopg/ .. _compiled Windows version: http://stickpeople.com/projects/python/win-psycopg/ .. _SQLite: http://www.sqlite.org/ .. _pysqlite: http://trac.edgewall.org/wiki/PySqlite diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 7bd56e92ec..652ad397ff 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -440,7 +440,7 @@ Handlers Django provides one log handler in addition to those provided by the Python logging module. -.. class:: AdminEmailHandler([include_html=False]) +.. class:: AdminEmailHandler(include_html=False, email_backend=None) This handler sends an email to the site admins for each log message it receives. @@ -470,13 +470,30 @@ Python logging module. with names and values of local variables at each level of the stack, plus the values of your Django settings. This information is potentially very sensitive, and you may not want to send it over email. Consider using - something such as `django-sentry`_ to get the best of both worlds -- the + something such as `Sentry`_ to get the best of both worlds -- the rich information of full tracebacks plus the security of *not* sending the information over email. You may also explicitly designate certain sensitive information to be filtered out of error reports -- learn more on :ref:`Filtering error reports<filtering-error-reports>`. -.. _django-sentry: http://pypi.python.org/pypi/django-sentry + .. versionadded:: 1.6 + + By setting the ``email_backend`` argument of ``AdminEmailHandler``, the + :ref:`email backend <topic-email-backends>` that is being used by the + handler can be overridden, like this:: + + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + 'email_backend': 'django.core.mail.backends.filebased.EmailBackend', + } + }, + + By default, an instance of the email backend specified in + :setting:`EMAIL_BACKEND` will be used. + +.. _Sentry: http://pypi.python.org/pypi/sentry Filters diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index f5749faaf2..e6dc165399 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -278,15 +278,13 @@ Iterators :: - class MyIterator(object): + class MyIterator(six.Iterator): def __iter__(self): return self # implement some logic here def __next__(self): raise StopIteration # implement some logic here - next = __next__ # Python 2 compatibility - Boolean evaluation ~~~~~~~~~~~~~~~~~~ @@ -297,7 +295,8 @@ Boolean evaluation def __bool__(self): return True # implement some logic here - __nonzero__ = __bool__ # Python 2 compatibility + def __nonzero__(self): # Python 2 compatibility + return type(self).__bool__(self) Division ~~~~~~~~ @@ -309,12 +308,14 @@ Division def __truediv__(self, other): return self / other # implement some logic here - __div__ = __truediv__ # Python 2 compatibility + def __div__(self, other): # Python 2 compatibility + return type(self).__truediv__(self, other) def __itruediv__(self, other): return self // other # implement some logic here - __idiv__ = __itruediv__ # Python 2 compatibility + def __idiv__(self, other): # Python 2 compatibility + return type(self).__itruediv__(self, other) .. module: django.utils.six diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 0a3c6bff02..169f9ac773 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -185,6 +185,31 @@ recommend you ensure your Web server is configured such that: Additionally, as of 1.3.1, Django requires you to explicitly enable support for the ``X-Forwarded-Host`` header if your configuration requires it. +Configuration for Apache +------------------------ + +The easiest way to get the described behavior in Apache is as follows. Create +a `virtual host`_ using the ServerName_ and ServerAlias_ directives to restrict +the domains Apache reacts to. Please keep in mind that while the directives do +support ports the match is only performed against the hostname. This means that +the ``Host`` header could still contain a port pointing to another webserver on +the same machine. The next step is to make sure that your newly created virtual +host is not also the default virtual host. Apache uses the first virtual host +found in the configuration file as default virtual host. As such you have to +ensure that you have another virtual host which will act as catch-all virtual +host. Just add one if you do not have one already, there is nothing special +about it aside from ensuring it is the first virtual host in the configuration +file. Debian/Ubuntu users usually don't have to take any action, since Apache +ships with a default virtual host in ``sites-available`` which is linked into +``sites-enabled`` as ``000-default`` and included from ``apache2.conf``. Just +make sure not to name your site ``000-abc``, since files are included in +alphabetical order. + +.. _virtual host: http://httpd.apache.org/docs/2.2/vhosts/ +.. _ServerName: http://httpd.apache.org/docs/2.2/mod/core.html#servername +.. _ServerAlias: http://httpd.apache.org/docs/2.2/mod/core.html#serveralias + + .. _additional-security-topics: Additional security topics diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 9b44166e42..28f600e223 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -166,15 +166,6 @@ Notes for specific serialization formats json ^^^^ -If you're using UTF-8 (or any other non-ASCII encoding) data with the JSON -serializer, you must pass ``ensure_ascii=False`` as a parameter to the -``serialize()`` call. Otherwise, the output won't be encoded correctly. - -For example:: - - json_serializer = serializers.get_serializer("json")() - json_serializer.serialize(queryset, ensure_ascii=False, stream=response) - Be aware that not all Django output can be passed unmodified to :mod:`json`. In particular, :ref:`lazy translation objects <lazy-translations>` need a `special encoder`_ written for them. Something like this will work:: diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index af45a8d95b..fb2119515b 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -16,9 +16,9 @@ or CheetahTemplate_, you should feel right at home with Django's templates. .. admonition:: Philosophy If you have a background in programming, or if you're used to languages - like PHP which mix programming code directly into HTML, you'll want to - bear in mind that the Django template system is not simply Python embedded - into HTML. This is by design: the template system is meant to express + which mix programming code directly into HTML, you'll want to bear in + mind that the Django template system is not simply Python embedded into + HTML. This is by design: the template system is meant to express presentation, not program logic. The Django template system provides tags which function similarly to some diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index d0b2e7cdf9..8c11e32a55 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -379,6 +379,15 @@ control the particular collation used by the test database. See the :doc:`settings documentation </ref/settings>` for details of these advanced settings. +.. admonition:: Finding data from your production database when running tests? + + If your code attempts to access the database when its modules are compiled, + this will occur *before* the test database is set up, with potentially + unexpected results. For example, if you have a database query in + module-level code and a real database exists, production data could pollute + your tests. *It is a bad idea to have such import-time database queries in + your code* anyway - rewrite your code so that it doesn't do this. + .. _topics-testing-masterslave: Testing master/slave configurations @@ -1177,10 +1186,13 @@ Normal Python unit test classes extend a base class of .. _testcase_hierarchy_diagram: -.. figure:: _images/django_unittest_classes_hierarchy.png +.. figure:: _images/django_unittest_classes_hierarchy.* :alt: Hierarchy of Django unit testing classes (TestCase subclasses) + :width: 508 + :height: 391 - Hierarchy of Django unit testing classes +Regardless of the version of Python you're using, if you've installed +:mod:`unittest2`, :mod:`django.utils.unittest` will point to that library. TestCase ^^^^^^^^ @@ -1586,15 +1598,24 @@ The decorator can also be applied to test case classes:: the original ``LoginTestCase`` is still equally affected by the decorator. -.. note:: +When overriding settings, make sure to handle the cases in which your app's +code uses a cache or similar feature that retains state even if the +setting is changed. Django provides the +:data:`django.test.signals.setting_changed` signal that lets you register +callbacks to clean up and otherwise reset state when settings are changed. + +Django itself uses this signal to reset various data: - When overriding settings, make sure to handle the cases in which your app's - code uses a cache or similar feature that retains state even if the - setting is changed. Django provides the - :data:`django.test.signals.setting_changed` signal that lets you register - callbacks to clean up and otherwise reset state when settings are changed. - Note that this signal isn't currently used by Django itself, so changing - built-in settings may not yield the results you expect. +================================ ======================== +Overriden settings Data reset +================================ ======================== +USE_TZ, TIME_ZONE Databases timezone +TEMPLATE_CONTEXT_PROCESSORS Context processors cache +TEMPLATE_LOADERS Template loaders cache +SERIALIZATION_MODULES Serializers cache +LOCALE_PATHS, LANGUAGE_CODE Default translation and loaded translations +MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage +================================ ======================== Emptying the test outbox ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1752,6 +1773,11 @@ your test suite. via an explicit ``order_by()`` call on the queryset prior to comparison. + .. versionchanged:: 1.6 + The method now checks for undefined order and raises ``ValueError`` + if undefined order is spotted. The ordering is seen as undefined if + the given ``qs`` isn't ordered and the comparison is against more + than one ordered values. .. method:: TestCase.assertNumQueries(num, func, *args, **kwargs) @@ -2075,6 +2101,7 @@ out the `full reference`_ for more details. def test_login(self): from selenium.webdriver.support.wait import WebDriverWait + timeout = 2 ... self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() # Wait until the response is received |
